penguin/monok8s

k8s image for Mono Gateway Dev Kit

clitools/pkg/render/agent.go

raw ยท 7128 bytes

package render

import (
	"fmt"
	"strings"

	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"

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

type AgentConf struct {
	Namespace        string
	Image            string
	ImagePullSecrets []string
	Labels           map[string]string
}

func RenderAgentDaemonSets(conf AgentConf) (string, error) {
	objs, err := buildAgentDaemonSetObjects(conf)
	if err != nil {
		return "", err
	}

	return renderObjects(objs)
}

func buildAgentDaemonSetObjects(conf AgentConf) ([]runtime.Object, error) {
	if strings.TrimSpace(conf.Namespace) == "" {
		return nil, fmt.Errorf("namespace is required")
	}

	conf.Labels = map[string]string{
		"app.kubernetes.io/name":       monov1alpha1.NodeAgentName,
		"app.kubernetes.io/component":  "agent",
		"app.kubernetes.io/part-of":    "monok8s",
		"app.kubernetes.io/managed-by": monov1alpha1.NodeControlName,
	}

	return []runtime.Object{
		buildAgentServiceAccount(conf),
		buildAgentClusterRole(conf),
		buildAgentClusterRoleBinding(conf),
		buildAgentDaemonSet(conf),
	}, nil
}

func buildAgentNamespace(conf AgentConf) *corev1.Namespace {
	return &corev1.Namespace{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "v1",
			Kind:       "Namespace",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:   conf.Namespace,
			Labels: copyStringMap(conf.Labels),
		},
	}
}

func buildAgentServiceAccount(conf AgentConf) *corev1.ServiceAccount {
	return &corev1.ServiceAccount{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "v1",
			Kind:       "ServiceAccount",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      monov1alpha1.NodeAgentName,
			Namespace: conf.Namespace,
			Labels:    copyStringMap(conf.Labels),
		},
	}
}

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

	return &rbacv1.ClusterRole{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "rbac.authorization.k8s.io/v1",
			Kind:       "ClusterRole",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:   monov1alpha1.NodeAgentName,
			Labels: copyStringMap(conf.Labels),
		},
		Rules: wantRules,
	}
}

func buildAgentClusterRoleBinding(conf AgentConf) *rbacv1.ClusterRoleBinding {
	return &rbacv1.ClusterRoleBinding{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "rbac.authorization.k8s.io/v1",
			Kind:       "ClusterRoleBinding",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:   monov1alpha1.NodeAgentName,
			Labels: copyStringMap(conf.Labels),
		},
		RoleRef: rbacv1.RoleRef{
			APIGroup: rbacv1.GroupName,
			Kind:     "ClusterRole",
			Name:     monov1alpha1.NodeAgentName,
		},
		Subjects: []rbacv1.Subject{
			{
				Kind:      "ServiceAccount",
				Name:      monov1alpha1.NodeAgentName,
				Namespace: conf.Namespace,
			},
		},
	}
}

func buildAgentDaemonSet(conf AgentConf) *appsv1.DaemonSet {
	privileged := true
	dsLabels := monov1alpha1.NodeAgentLabels()

	image, pullPolicy := agentImage(conf)

	return &appsv1.DaemonSet{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "apps/v1",
			Kind:       "DaemonSet",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      monov1alpha1.NodeAgentName,
			Namespace: conf.Namespace,
			Labels:    copyStringMap(conf.Labels),
		},
		Spec: appsv1.DaemonSetSpec{
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"app.kubernetes.io/name": monov1alpha1.NodeAgentName,
				},
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: dsLabels,
				},
				Spec: corev1.PodSpec{
					ServiceAccountName: monov1alpha1.NodeAgentName,
					HostNetwork:        true,
					HostPID:            true,
					DNSPolicy:          corev1.DNSClusterFirstWithHostNet,
					ImagePullSecrets:   imagePullSecrets(conf.ImagePullSecrets),
					NodeSelector: map[string]string{
						monov1alpha1.NodeControlKey: "true",
					},
					Tolerations: []corev1.Toleration{
						{Operator: corev1.TolerationOpExists},
					},
					Containers: []corev1.Container{
						{
							Name:            "agent",
							Image:           image,
							ImagePullPolicy: pullPolicy,
							Args:            []string{"agent", "--env-file", "$(CLUSTER_ENV_FILE)"},
							Env: []corev1.EnvVar{
								{
									Name: "NODE_NAME",
									ValueFrom: &corev1.EnvVarSource{
										FieldRef: &corev1.ObjectFieldSelector{
											APIVersion: "v1",
											FieldPath:  "spec.nodeName",
										},
									},
								},
								{
									Name:  "CLUSTER_ENV_FILE",
									Value: "/host/opt/monok8s/config/cluster.env",
								},
								{
									Name:  "FW_ENV_CONFIG_FILE",
									Value: "/host/etc/fw_env.config",
								},
							},
							SecurityContext: &corev1.SecurityContext{
								Privileged: &privileged,
							},
							VolumeMounts: []corev1.VolumeMount{
								{
									Name:      "host-dev",
									MountPath: "/dev",
								},
								{
									Name:      "host-etc",
									MountPath: "/host/etc",
									ReadOnly:  true,
								},
								{
									Name:      "host-config",
									MountPath: "/host/opt/monok8s/config",
									ReadOnly:  true,
								},
							},
						},
					},
					Volumes: []corev1.Volume{
						{
							Name: "host-dev",
							VolumeSource: corev1.VolumeSource{
								HostPath: &corev1.HostPathVolumeSource{
									Path: "/dev",
									Type: hostPathType(corev1.HostPathDirectory),
								},
							},
						},
						{
							Name: "host-etc",
							VolumeSource: corev1.VolumeSource{
								HostPath: &corev1.HostPathVolumeSource{
									Path: "/etc",
									Type: hostPathType(corev1.HostPathDirectory),
								},
							},
						},
						{
							Name: "host-config",
							VolumeSource: corev1.VolumeSource{
								HostPath: &corev1.HostPathVolumeSource{
									Path: "/opt/monok8s/config",
									Type: hostPathType(corev1.HostPathDirectory),
								},
							},
						},
					},
				},
			},
		},
	}
}

func agentImage(conf AgentConf) (string, corev1.PullPolicy) {
	if conf.Image != "" {
		return conf.Image, corev1.PullIfNotPresent
	}

	return fmt.Sprintf("localhost/monok8s/node-control:%s", buildinfo.Version), corev1.PullNever
}

func copyStringMap(in map[string]string) map[string]string {
	if len(in) == 0 {
		return nil
	}

	out := make(map[string]string, len(in))
	for k, v := range in {
		out[k] = v
	}

	return out
}

func hostPathType(t corev1.HostPathType) *corev1.HostPathType {
	return &t
}