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 }