package cloudinit import ( "fmt" "log/slog" "os" "path/filepath" "strings" ) // ApplyPortainer writes the Portainer Edge Agent deployment manifest // based on cloud-init config. The manifest is applied by KubeSolo after // the cluster is ready. func ApplyPortainer(cfg *Config, manifestDir string) error { if !cfg.Portainer.EdgeAgent.Enabled { slog.Info("portainer edge agent not enabled, skipping") return nil } ea := cfg.Portainer.EdgeAgent if ea.EdgeID == "" || ea.EdgeKey == "" { return fmt.Errorf("portainer edge-agent enabled but edge-id and edge-key are required") } if ea.PortainerURL == "" { return fmt.Errorf("portainer edge-agent enabled but portainer-url is required") } image := ea.Image if image == "" { image = "portainer/agent:latest" } if err := os.MkdirAll(manifestDir, 0o755); err != nil { return fmt.Errorf("creating manifest dir: %w", err) } manifest := buildEdgeAgentManifest(ea.EdgeID, ea.EdgeKey, ea.PortainerURL, image) dest := filepath.Join(manifestDir, "portainer-edge-agent.yaml") if err := os.WriteFile(dest, []byte(manifest), 0o644); err != nil { return fmt.Errorf("writing edge agent manifest: %w", err) } slog.Info("portainer edge agent manifest written", "path", dest) return nil } func buildEdgeAgentManifest(edgeID, edgeKey, portainerURL, image string) string { var sb strings.Builder sb.WriteString("# Auto-generated by KubeSolo OS cloud-init\n") sb.WriteString("# Portainer Edge Agent deployment\n") sb.WriteString("---\n") sb.WriteString("apiVersion: v1\n") sb.WriteString("kind: Namespace\n") sb.WriteString("metadata:\n") sb.WriteString(" name: portainer\n") sb.WriteString(" labels:\n") sb.WriteString(" app.kubernetes.io/name: portainer-agent\n") sb.WriteString(" app.kubernetes.io/component: edge-agent\n") sb.WriteString("---\n") sb.WriteString("apiVersion: v1\n") sb.WriteString("kind: ServiceAccount\n") sb.WriteString("metadata:\n") sb.WriteString(" name: portainer-sa-clusteradmin\n") sb.WriteString(" namespace: portainer\n") sb.WriteString("---\n") sb.WriteString("apiVersion: rbac.authorization.k8s.io/v1\n") sb.WriteString("kind: ClusterRoleBinding\n") sb.WriteString("metadata:\n") sb.WriteString(" name: portainer-crb-clusteradmin\n") sb.WriteString("roleRef:\n") sb.WriteString(" apiGroup: rbac.authorization.k8s.io\n") sb.WriteString(" kind: ClusterRole\n") sb.WriteString(" name: cluster-admin\n") sb.WriteString("subjects:\n") sb.WriteString(" - kind: ServiceAccount\n") sb.WriteString(" name: portainer-sa-clusteradmin\n") sb.WriteString(" namespace: portainer\n") sb.WriteString("---\n") sb.WriteString("apiVersion: v1\n") sb.WriteString("kind: Service\n") sb.WriteString("metadata:\n") sb.WriteString(" name: portainer-agent\n") sb.WriteString(" namespace: portainer\n") sb.WriteString("spec:\n") sb.WriteString(" clusterIP: None\n") sb.WriteString(" selector:\n") sb.WriteString(" app: portainer-agent\n") sb.WriteString(" ports:\n") sb.WriteString(" - name: agent\n") sb.WriteString(" port: 9001\n") sb.WriteString(" targetPort: 9001\n") sb.WriteString(" protocol: TCP\n") sb.WriteString("---\n") sb.WriteString("apiVersion: apps/v1\n") sb.WriteString("kind: Deployment\n") sb.WriteString("metadata:\n") sb.WriteString(" name: portainer-agent\n") sb.WriteString(" namespace: portainer\n") sb.WriteString(" labels:\n") sb.WriteString(" app.kubernetes.io/name: portainer-agent\n") sb.WriteString(" app.kubernetes.io/component: edge-agent\n") sb.WriteString("spec:\n") sb.WriteString(" replicas: 1\n") sb.WriteString(" selector:\n") sb.WriteString(" matchLabels:\n") sb.WriteString(" app: portainer-agent\n") sb.WriteString(" template:\n") sb.WriteString(" metadata:\n") sb.WriteString(" labels:\n") sb.WriteString(" app: portainer-agent\n") sb.WriteString(" spec:\n") sb.WriteString(" serviceAccountName: portainer-sa-clusteradmin\n") sb.WriteString(" containers:\n") sb.WriteString(" - name: agent\n") sb.WriteString(fmt.Sprintf(" image: %s\n", image)) sb.WriteString(" env:\n") sb.WriteString(" - name: EDGE\n") sb.WriteString(" value: \"1\"\n") sb.WriteString(" - name: EDGE_ID\n") sb.WriteString(fmt.Sprintf(" value: \"%s\"\n", edgeID)) sb.WriteString(" - name: EDGE_KEY\n") sb.WriteString(fmt.Sprintf(" value: \"%s\"\n", edgeKey)) sb.WriteString(" - name: EDGE_INSECURE_POLL\n") sb.WriteString(" value: \"1\"\n") sb.WriteString(" - name: KUBERNETES_POD_IP\n") sb.WriteString(" valueFrom:\n") sb.WriteString(" fieldRef:\n") sb.WriteString(" fieldPath: status.podIP\n") sb.WriteString(" ports:\n") sb.WriteString(" - containerPort: 9001\n") sb.WriteString(" protocol: TCP\n") sb.WriteString(" resources:\n") sb.WriteString(" requests:\n") sb.WriteString(" memory: 64Mi\n") sb.WriteString(" cpu: 50m\n") sb.WriteString(" limits:\n") sb.WriteString(" memory: 256Mi\n") sb.WriteString(" cpu: 500m\n") sb.WriteString(" volumeMounts:\n") sb.WriteString(" - name: docker-certs\n") sb.WriteString(" mountPath: /certs\n") sb.WriteString(" readOnly: true\n") sb.WriteString(" volumes:\n") sb.WriteString(" - name: docker-certs\n") sb.WriteString(" emptyDir: {}\n") sb.WriteString(" tolerations:\n") sb.WriteString(" - operator: Exists\n") return sb.String() }