feat: add bootstrap workflow
This commit is contained in:
parent
cc8fab6832
commit
85e87f1334
13
.vscode/launch.json
vendored
13
.vscode/launch.json
vendored
|
|
@ -44,6 +44,19 @@
|
|||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Run and Debug CICD Tool ghcr auth creation",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}",
|
||||
"args": [
|
||||
"vault",
|
||||
"create-ghcr-secret"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
2
How2Auth.md
Normal file
2
How2Auth.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
edingrech
|
||||
dckr_pat_bfKKDH4g3qUxchs9UMLxFx2oTiU
|
||||
1
MaidnCLI
Submodule
1
MaidnCLI
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit cc8fab6832a3a51bd072155f90f91354e440077a
|
||||
|
|
@ -11,6 +11,12 @@ go install github.com/go-delve/delve/cmd/dlv@latest
|
|||
dlv version
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
- `cicd-tool repo init --org <org> --flux-repo <repo>` creates the manifests and Flux repos
|
||||
- `cicd-tool bootstrap` runs the user-facing wizard, saves a config YAML, clones the Talos IaC repo, writes `terraform.tfvars`, stages Talos images on Proxmox when enabled, and can execute Terraform, Talos bootstrap, and Flux bootstrap
|
||||
- `cicd-tool bootstrap --config maidn-bootstrap.yaml` skips the wizard and uses the saved config
|
||||
|
||||
## TODO
|
||||
create the file:
|
||||
within the main flux repo dynamically changing the org and repo location based on the user's input
|
||||
|
|
|
|||
53
cmd/bootstrap.go
Normal file
53
cmd/bootstrap.go
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/Pingu-Studio/MaidnCLI/internal/bootstrap"
|
||||
"github.com/Pingu-Studio/MaidnCLI/internal/config"
|
||||
"github.com/Pingu-Studio/MaidnCLI/internal/ui"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var bootstrapConfigPath string
|
||||
var bootstrapOutputPath string
|
||||
|
||||
var bootstrapCmd = &cobra.Command{
|
||||
Use: "bootstrap",
|
||||
Short: "Bootstrap Talos and Flux from config or an interactive wizard.",
|
||||
Run: runBootstrap,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(bootstrapCmd)
|
||||
bootstrapCmd.Flags().StringVar(&bootstrapConfigPath, "config", "", "Path to bootstrap config YAML")
|
||||
bootstrapCmd.Flags().StringVar(&bootstrapOutputPath, "out", "maidn-bootstrap.yaml", "Path to save generated config")
|
||||
}
|
||||
|
||||
func runBootstrap(cmd *cobra.Command, args []string) {
|
||||
var cfg config.Config
|
||||
var err error
|
||||
|
||||
if bootstrapConfigPath != "" {
|
||||
cfg, err = config.Load(bootstrapConfigPath)
|
||||
} else {
|
||||
cfg, err = ui.RunBootstrapWizard()
|
||||
if err == nil {
|
||||
err = config.Save(bootstrapOutputPath, cfg)
|
||||
if err == nil {
|
||||
fmt.Printf("[INFO] Saved config to %s\n", bootstrapOutputPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Printf("[ERROR] %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
runner := bootstrap.Runner{Config: cfg}
|
||||
if err = runner.Run(); err != nil {
|
||||
fmt.Printf("[ERROR] %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -48,8 +48,10 @@ func runInitRepo(cmd *cobra.Command, args []string) {
|
|||
fmt.Println("[SUCCESS] Dependencies and authentication are OK.")
|
||||
|
||||
repoManager := github.NewRepoManager(orgName, manifestsRepoName, fluxRepoName)
|
||||
repoManager.InitializeManifestsRepo()
|
||||
repoManager.InitializeFluxRepo()
|
||||
if err := repoManager.InitializeAll(); err != nil {
|
||||
fmt.Printf("[ERROR] %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("\n🎉 Onboarding complete! 🎉")
|
||||
fmt.Printf("✅ Manifests repository: https://github.com/%s/%s\n", orgName, manifestsRepoName)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ var createDockerSecretCmd = &cobra.Command{
|
|||
|
||||
var createGhcrSecretCmd = &cobra.Command{
|
||||
Use: "create-ghcr-secret",
|
||||
Short: "Creates secrets in Vault for GHCR.",
|
||||
Short: "Creates secrets in Vault for GHCR and applies it to Kubernetes.",
|
||||
Run: runCreateGhcrSecret,
|
||||
}
|
||||
|
||||
|
|
|
|||
22
docs/maintenance.md
Normal file
22
docs/maintenance.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Cluster maintenance
|
||||
|
||||
## What the CLI handles now
|
||||
|
||||
- Bootstraps the Talos IaC repo
|
||||
- Writes cluster-specific Terraform config
|
||||
- Can trigger Terraform, Talos bootstrap, kubeconfig generation, and Flux bootstrap
|
||||
- Can request Proxmox-side Talos ISO staging through Terraform inputs
|
||||
|
||||
## Manual operations still required
|
||||
|
||||
- Talos upgrades remain operator-driven
|
||||
- Certificate rotation remains operator-driven
|
||||
- etcd snapshots and restore drills remain operator-driven
|
||||
|
||||
## Candidate automation
|
||||
|
||||
- Add a `cicd-tool talos upgrade` workflow that updates version pins, stages images, and rolls nodes one by one
|
||||
- Add a `cicd-tool talos certs check` workflow that reports expiry windows
|
||||
- Add a scheduled GitHub Action in the Talos repo that opens PRs for new factory-compatible image pins
|
||||
|
||||
Use the standalone Talos repo maintenance guide for the actual runbook: `maidn/maidn-talos-proxmox/docs/maintenance.md`.
|
||||
159
internal/bootstrap/bootstrap.go
Normal file
159
internal/bootstrap/bootstrap.go
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
package bootstrap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Pingu-Studio/MaidnCLI/internal/config"
|
||||
ghrepo "github.com/Pingu-Studio/MaidnCLI/internal/github"
|
||||
"github.com/Pingu-Studio/MaidnCLI/internal/utils"
|
||||
)
|
||||
|
||||
type Runner struct {
|
||||
Config config.Config
|
||||
}
|
||||
|
||||
func (r Runner) Run() error {
|
||||
workspace := r.Config.WorkspaceDir
|
||||
if err := os.MkdirAll(workspace, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ghrepo.NewRepoManager(r.Config.GitHub.Org, "cicd-deployment-manifests", r.Config.Flux.RepoName).InitializeAll(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repoDir := filepath.Join(workspace, r.Config.Talos.RepoDirName)
|
||||
if err := ensureRepo(repoDir, r.Config.Talos.RepoURL, r.Config.Talos.RepoRef); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
terraformDir := filepath.Join(repoDir, r.Config.Talos.TerraformDir)
|
||||
generatedDir := filepath.Join(repoDir, r.Config.Talos.GeneratedDir)
|
||||
if err := os.MkdirAll(generatedDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filepath.Join(terraformDir, r.Config.Talos.ConfigFileName), []byte(renderTerraformTFVars(r.Config)), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.Config.Talos.AutoRunTerraform {
|
||||
if err := utils.RunCommandInDir(terraformDir, "terraform", "init"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := utils.RunCommandInDir(terraformDir, "terraform", "apply", "-auto-approve"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if r.Config.Talos.AutoBootstrap {
|
||||
if err := utils.RunCommandInDir(generatedDir, "talosctl", "bootstrap", "--talosconfig=./clusterconfig/talosconfig", "--nodes="+r.Config.Talos.BootstrapNode); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := utils.RunCommandInDir(generatedDir, "talosctl", "kubeconfig", "--talosconfig=./clusterconfig/talosconfig", "--nodes="+r.Config.Talos.KubeconfigNode, "."); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if r.Config.Talos.AutoBootstrapFlux {
|
||||
if err := utils.RunCommand("flux", "bootstrap", "github", "--owner="+r.Config.Flux.Owner, "--repository="+r.Config.Flux.RepoName, "--branch="+r.Config.Flux.Branch, "--path="+r.Config.Flux.ClusterPath, "--cluster-domain="+r.Config.Flux.ClusterDomain); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureRepo(dir, repoURL, ref string) error {
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
return utils.RunCommand("git", "clone", "--branch", ref, repoURL, dir)
|
||||
}
|
||||
if err := utils.RunCommandInDir(dir, "git", "fetch", "origin"); err != nil {
|
||||
return err
|
||||
}
|
||||
return utils.RunCommandInDir(dir, "git", "checkout", ref)
|
||||
}
|
||||
|
||||
func renderTerraformTFVars(cfg config.Config) string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString(fmt.Sprintf("proxmox_api_url = %q\n", cfg.Talos.Proxmox.APIURL))
|
||||
builder.WriteString(fmt.Sprintf("proxmox_node = %q\n", cfg.Talos.Proxmox.DefaultNode))
|
||||
builder.WriteString(fmt.Sprintf("proxmox_api_token = %q\n", cfg.Talos.Proxmox.APIToken))
|
||||
builder.WriteString(fmt.Sprintf("proxmox_pool = %q\n", cfg.Talos.Proxmox.Pool))
|
||||
builder.WriteString(fmt.Sprintf("cluster_name = %q\n", cfg.Talos.Cluster.Name))
|
||||
builder.WriteString(fmt.Sprintf("cluster_domain = %q\n", cfg.Talos.Cluster.Domain))
|
||||
builder.WriteString(fmt.Sprintf("talos_factory_schematic_id = %q\n", cfg.Talos.Image.FactorySchematicID))
|
||||
builder.WriteString(fmt.Sprintf("talos_version = %q\n", cfg.Talos.Image.TalosVersion))
|
||||
builder.WriteString(fmt.Sprintf("cni_name = %q\n", cfg.Talos.Cluster.CNI))
|
||||
builder.WriteString(fmt.Sprintf("talos_iso_file = %q\n", cfg.Talos.Image.TalosISOFile))
|
||||
updateMode := cfg.Talos.Image.UpdateMode
|
||||
if updateMode == "" {
|
||||
updateMode = "manual"
|
||||
}
|
||||
storage := cfg.Talos.Image.Storage
|
||||
if storage == "" {
|
||||
storage = "local"
|
||||
}
|
||||
architecture := cfg.Talos.Image.Architecture
|
||||
if architecture == "" {
|
||||
architecture = "amd64"
|
||||
}
|
||||
builder.WriteString(fmt.Sprintf("talos_image_update_mode = %q\n", updateMode))
|
||||
builder.WriteString(fmt.Sprintf("talos_image_storage = %q\n", storage))
|
||||
builder.WriteString(fmt.Sprintf("talos_image_architecture = %q\n", architecture))
|
||||
builder.WriteString(fmt.Sprintf("disk_storage = %q\n", cfg.Talos.Cluster.DiskStorage))
|
||||
builder.WriteString(fmt.Sprintf("additional_disk_storage = %q\n", cfg.Talos.Cluster.AdditionalStorage))
|
||||
builder.WriteString(fmt.Sprintf("dns_servers = [%s]\n", quoteList(cfg.Talos.Cluster.DNSServers)))
|
||||
builder.WriteString(fmt.Sprintf("control_plane_vip = %q\n", cfg.Talos.Cluster.ControlPlaneVIP))
|
||||
builder.WriteString("node_interfaces = {\n")
|
||||
for node, iface := range cfg.Talos.Proxmox.NodeInterfaces {
|
||||
builder.WriteString(fmt.Sprintf(" %q = %q\n", node, iface))
|
||||
}
|
||||
builder.WriteString("}\n")
|
||||
builder.WriteString(fmt.Sprintf("create_vlan_interface = %t\n", cfg.Talos.Cluster.CreateVLANInterface))
|
||||
builder.WriteString("image_cache_proxy = { enabled = false, ip = \"\", port = 3128 }\n")
|
||||
builder.WriteString("nodes = [\n")
|
||||
for _, node := range cfg.Talos.Nodes {
|
||||
builder.WriteString(" {\n")
|
||||
builder.WriteString(fmt.Sprintf(" name = %q\n", node.Name))
|
||||
builder.WriteString(fmt.Sprintf(" vmid = %d\n", node.VMID))
|
||||
builder.WriteString(fmt.Sprintf(" role = %q\n", node.Role))
|
||||
builder.WriteString(fmt.Sprintf(" cores = %d\n", node.Cores))
|
||||
builder.WriteString(fmt.Sprintf(" memory = %d\n", node.Memory))
|
||||
builder.WriteString(fmt.Sprintf(" disk_size = %q\n", node.DiskSize))
|
||||
if node.AdditionalDiskSize != "" {
|
||||
builder.WriteString(fmt.Sprintf(" additional_disk_size = %q\n", node.AdditionalDiskSize))
|
||||
}
|
||||
builder.WriteString(fmt.Sprintf(" tags = [%s]\n", quoteList(node.Tags)))
|
||||
builder.WriteString(fmt.Sprintf(" proxmox_node = %q\n", node.ProxmoxNode))
|
||||
builder.WriteString(" networks = [\n")
|
||||
for _, net := range node.Networks {
|
||||
builder.WriteString(" {\n")
|
||||
builder.WriteString(fmt.Sprintf(" mac_address = %q\n", net.MACAddress))
|
||||
builder.WriteString(fmt.Sprintf(" cidr = %q\n", net.CIDR))
|
||||
if net.IP != "" {
|
||||
builder.WriteString(fmt.Sprintf(" ip = %q\n", net.IP))
|
||||
}
|
||||
if net.Gateway != "" {
|
||||
builder.WriteString(fmt.Sprintf(" gateway = %q\n", net.Gateway))
|
||||
}
|
||||
builder.WriteString(fmt.Sprintf(" vlan_id = %d\n", net.VLANID))
|
||||
builder.WriteString(" },\n")
|
||||
}
|
||||
builder.WriteString(" ]\n")
|
||||
builder.WriteString(" },\n")
|
||||
}
|
||||
builder.WriteString("]\n")
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func quoteList(values []string) string {
|
||||
quoted := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
quoted = append(quoted, fmt.Sprintf("%q", value))
|
||||
}
|
||||
return strings.Join(quoted, ", ")
|
||||
}
|
||||
65
internal/config/config.go
Normal file
65
internal/config/config.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func Load(path string) (Config, error) {
|
||||
var cfg Config
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
return cfg, Validate(cfg)
|
||||
}
|
||||
|
||||
func Save(path string, cfg Config) error {
|
||||
if err := Validate(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, data, 0644)
|
||||
}
|
||||
|
||||
func Validate(cfg Config) error {
|
||||
if cfg.WorkspaceDir == "" {
|
||||
return errors.New("workspaceDir is required")
|
||||
}
|
||||
if cfg.GitHub.Org == "" {
|
||||
return errors.New("github.org is required")
|
||||
}
|
||||
if cfg.Flux.RepoName == "" || cfg.Flux.ClusterPath == "" {
|
||||
return errors.New("flux repoName and clusterPath are required")
|
||||
}
|
||||
if cfg.Talos.RepoURL == "" || cfg.Talos.RepoDirName == "" {
|
||||
return errors.New("talos repoUrl and repoDirName are required")
|
||||
}
|
||||
if cfg.Talos.TerraformDir == "" || cfg.Talos.GeneratedDir == "" || cfg.Talos.ConfigFileName == "" {
|
||||
return errors.New("talos terraformDir, generatedDir, and configFileName are required")
|
||||
}
|
||||
if cfg.Talos.Proxmox.APIURL == "" || cfg.Talos.Proxmox.APIToken == "" {
|
||||
return errors.New("talos proxmox apiUrl and apiToken are required")
|
||||
}
|
||||
if cfg.Talos.Cluster.Name == "" || cfg.Talos.Cluster.Domain == "" {
|
||||
return errors.New("talos cluster name and domain are required")
|
||||
}
|
||||
if cfg.Talos.Image.FactorySchematicID == "" || cfg.Talos.Image.TalosVersion == "" || cfg.Talos.Image.TalosISOFile == "" {
|
||||
return errors.New("talos image pins are required")
|
||||
}
|
||||
if cfg.Talos.Image.UpdateMode != "" && cfg.Talos.Image.UpdateMode != "manual" && cfg.Talos.Image.UpdateMode != "download" {
|
||||
return errors.New("talos image updateMode must be manual or download")
|
||||
}
|
||||
if len(cfg.Talos.Nodes) == 0 {
|
||||
return errors.New("at least one talos node is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
87
internal/config/types.go
Normal file
87
internal/config/types.go
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
package config
|
||||
|
||||
type Config struct {
|
||||
WorkspaceDir string `yaml:"workspaceDir"`
|
||||
GitHub GitHubConfig `yaml:"github"`
|
||||
Flux FluxConfig `yaml:"flux"`
|
||||
Talos TalosConfig `yaml:"talos"`
|
||||
}
|
||||
|
||||
type GitHubConfig struct {
|
||||
Org string `yaml:"org"`
|
||||
}
|
||||
|
||||
type FluxConfig struct {
|
||||
RepoName string `yaml:"repoName"`
|
||||
Branch string `yaml:"branch"`
|
||||
ClusterPath string `yaml:"clusterPath"`
|
||||
ClusterDomain string `yaml:"clusterDomain"`
|
||||
Owner string `yaml:"owner"`
|
||||
}
|
||||
|
||||
type TalosConfig struct {
|
||||
RepoURL string `yaml:"repoUrl"`
|
||||
RepoRef string `yaml:"repoRef"`
|
||||
RepoDirName string `yaml:"repoDirName"`
|
||||
TerraformDir string `yaml:"terraformDir"`
|
||||
GeneratedDir string `yaml:"generatedDir"`
|
||||
ConfigFileName string `yaml:"configFileName"`
|
||||
AutoRunTerraform bool `yaml:"autoRunTerraform"`
|
||||
AutoBootstrap bool `yaml:"autoBootstrap"`
|
||||
AutoBootstrapFlux bool `yaml:"autoBootstrapFlux"`
|
||||
BootstrapNode string `yaml:"bootstrapNode"`
|
||||
KubeconfigNode string `yaml:"kubeconfigNode"`
|
||||
Proxmox TalosProxmoxConfig `yaml:"proxmox"`
|
||||
Cluster TalosClusterConfig `yaml:"cluster"`
|
||||
Image TalosImageConfig `yaml:"image"`
|
||||
Nodes []TalosNode `yaml:"nodes"`
|
||||
}
|
||||
|
||||
type TalosProxmoxConfig struct {
|
||||
APIURL string `yaml:"apiUrl"`
|
||||
APIToken string `yaml:"apiToken"`
|
||||
DefaultNode string `yaml:"defaultNode"`
|
||||
Pool string `yaml:"pool"`
|
||||
NodeInterfaces map[string]string `yaml:"nodeInterfaces"`
|
||||
}
|
||||
|
||||
type TalosClusterConfig struct {
|
||||
Name string `yaml:"name"`
|
||||
Domain string `yaml:"domain"`
|
||||
ControlPlaneVIP string `yaml:"controlPlaneVip"`
|
||||
CNI string `yaml:"cni"`
|
||||
DNSServers []string `yaml:"dnsServers"`
|
||||
DiskStorage string `yaml:"diskStorage"`
|
||||
AdditionalStorage string `yaml:"additionalStorage"`
|
||||
CreateVLANInterface bool `yaml:"createVlanInterface"`
|
||||
}
|
||||
|
||||
type TalosImageConfig struct {
|
||||
FactorySchematicID string `yaml:"factorySchematicId"`
|
||||
TalosVersion string `yaml:"talosVersion"`
|
||||
TalosISOFile string `yaml:"talosIsoFile"`
|
||||
UpdateMode string `yaml:"updateMode"`
|
||||
Storage string `yaml:"storage"`
|
||||
Architecture string `yaml:"architecture"`
|
||||
}
|
||||
|
||||
type TalosNode struct {
|
||||
Name string `yaml:"name"`
|
||||
VMID int `yaml:"vmid"`
|
||||
Role string `yaml:"role"`
|
||||
Cores int `yaml:"cores"`
|
||||
Memory int `yaml:"memory"`
|
||||
DiskSize string `yaml:"diskSize"`
|
||||
AdditionalDiskSize string `yaml:"additionalDiskSize,omitempty"`
|
||||
Tags []string `yaml:"tags"`
|
||||
ProxmoxNode string `yaml:"proxmoxNode"`
|
||||
Networks []TalosNetwork `yaml:"networks"`
|
||||
}
|
||||
|
||||
type TalosNetwork struct {
|
||||
MACAddress string `yaml:"macAddress"`
|
||||
CIDR string `yaml:"cidr"`
|
||||
IP string `yaml:"ip,omitempty"`
|
||||
Gateway string `yaml:"gateway,omitempty"`
|
||||
VLANID int `yaml:"vlanId"`
|
||||
}
|
||||
|
|
@ -154,6 +154,12 @@ func (rm *RepoManager) InitializeFluxRepo() {
|
|||
rm.setupRepository(fullRepoName, rm.createFluxStructure)
|
||||
}
|
||||
|
||||
func (rm *RepoManager) InitializeAll() error {
|
||||
rm.InitializeManifestsRepo()
|
||||
rm.InitializeFluxRepo()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rm *RepoManager) setupRepository(fullRepoName string, structureFunc func(string)) {
|
||||
tempDir, err := os.MkdirTemp("", "repo-setup-*")
|
||||
if err != nil {
|
||||
|
|
|
|||
164
internal/ui/wizard.go
Normal file
164
internal/ui/wizard.go
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Pingu-Studio/MaidnCLI/internal/config"
|
||||
)
|
||||
|
||||
func RunBootstrapWizard() (config.Config, error) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
cfg := config.Config{}
|
||||
|
||||
cfg.WorkspaceDir = prompt(reader, "Workspace directory", ".\\maidn-workspace")
|
||||
cfg.GitHub.Org = prompt(reader, "GitHub organization", "Pingu-Studio")
|
||||
cfg.Flux.Owner = cfg.GitHub.Org
|
||||
cfg.Flux.RepoName = prompt(reader, "Flux repo name", "mcast-maidn-cd-cluster")
|
||||
cfg.Flux.Branch = prompt(reader, "Flux branch", "main")
|
||||
cfg.Flux.ClusterPath = prompt(reader, "Flux cluster path", "./clusters/maidn-cd-0")
|
||||
cfg.Flux.ClusterDomain = prompt(reader, "Flux cluster domain", "dev01.nid3.com")
|
||||
|
||||
cfg.Talos.RepoURL = prompt(reader, "Talos IaC repo URL", "https://github.com/Pingu-Studio/maidn-talos-proxmox.git")
|
||||
cfg.Talos.RepoRef = prompt(reader, "Talos IaC repo ref", "main")
|
||||
cfg.Talos.RepoDirName = prompt(reader, "Talos IaC checkout directory", "maidn-talos-proxmox")
|
||||
cfg.Talos.TerraformDir = prompt(reader, "Terraform subdirectory", "terraform")
|
||||
cfg.Talos.GeneratedDir = prompt(reader, "Generated subdirectory", "generated")
|
||||
cfg.Talos.ConfigFileName = prompt(reader, "Terraform config filename", "terraform.tfvars")
|
||||
cfg.Talos.AutoRunTerraform = promptBool(reader, "Run terraform automatically", true)
|
||||
cfg.Talos.AutoBootstrap = promptBool(reader, "Run talos bootstrap automatically", true)
|
||||
cfg.Talos.AutoBootstrapFlux = promptBool(reader, "Run flux bootstrap automatically", true)
|
||||
|
||||
cfg.Talos.Proxmox.APIURL = prompt(reader, "Proxmox API URL", "https://192.168.0.9:8006/api2/json")
|
||||
cfg.Talos.Proxmox.APIToken = prompt(reader, "Proxmox API token", "")
|
||||
cfg.Talos.Proxmox.DefaultNode = prompt(reader, "Default Proxmox node", "pingu4")
|
||||
cfg.Talos.Proxmox.Pool = prompt(reader, "Proxmox pool", "")
|
||||
cfg.Talos.Proxmox.NodeInterfaces = promptMap(reader, "Node interfaces map (node=iface,comma separated)", map[string]string{"pingu4": "enp6s0"})
|
||||
|
||||
cfg.Talos.Cluster.Name = prompt(reader, "Talos cluster name", "proxmox-talos-dev01-cluster")
|
||||
cfg.Talos.Cluster.Domain = prompt(reader, "Talos cluster domain", "dev01.nid3.com")
|
||||
cfg.Talos.Cluster.ControlPlaneVIP = prompt(reader, "Control plane VIP", "192.168.43.7")
|
||||
cfg.Talos.Cluster.CNI = prompt(reader, "CNI", "flannel")
|
||||
cfg.Talos.Cluster.DNSServers = promptList(reader, "DNS servers (comma separated)", []string{"192.168.0.222", "1.1.1.1"})
|
||||
cfg.Talos.Cluster.DiskStorage = prompt(reader, "Primary disk storage", "local-lvm")
|
||||
cfg.Talos.Cluster.AdditionalStorage = prompt(reader, "Additional disk storage", "local-lvm")
|
||||
cfg.Talos.Cluster.CreateVLANInterface = promptBool(reader, "Create VLAN interfaces", true)
|
||||
|
||||
cfg.Talos.Image.FactorySchematicID = prompt(reader, "Talos factory schematic ID", "ddf38e55e30aa9b2bb0b765054ed63444dc00244ab8bb4ad4ad92486602285f8")
|
||||
cfg.Talos.Image.TalosVersion = prompt(reader, "Talos version", "v1.11.1")
|
||||
cfg.Talos.Image.TalosISOFile = prompt(reader, "Talos ISO file", "local:iso/talos-1.11.1.iso")
|
||||
cfg.Talos.Image.UpdateMode = prompt(reader, "Talos image update mode (manual/download)", "manual")
|
||||
cfg.Talos.Image.Storage = prompt(reader, "Talos image storage", "local")
|
||||
cfg.Talos.Image.Architecture = prompt(reader, "Talos image architecture", "amd64")
|
||||
|
||||
cfg.Talos.Nodes = promptNodes(reader, cfg.Talos.Proxmox.DefaultNode)
|
||||
if len(cfg.Talos.Nodes) > 0 {
|
||||
cfg.Talos.BootstrapNode = prompt(reader, "Talos bootstrap node IP", cfg.Talos.Nodes[0].Networks[0].IP)
|
||||
cfg.Talos.KubeconfigNode = prompt(reader, "Talos kubeconfig node IP", cfg.Talos.Nodes[0].Networks[0].IP)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func prompt(reader *bufio.Reader, label, fallback string) string {
|
||||
if fallback != "" {
|
||||
fmt.Printf("%s [%s]: ", label, fallback)
|
||||
} else {
|
||||
fmt.Printf("%s: ", label)
|
||||
}
|
||||
value, _ := reader.ReadString('\n')
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func promptBool(reader *bufio.Reader, label string, fallback bool) bool {
|
||||
defaultValue := "y"
|
||||
if !fallback {
|
||||
defaultValue = "n"
|
||||
}
|
||||
fmt.Printf("%s [y/n, default %s]: ", label, defaultValue)
|
||||
value, _ := reader.ReadString('\n')
|
||||
value = strings.TrimSpace(strings.ToLower(value))
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
return value == "y" || value == "yes"
|
||||
}
|
||||
|
||||
func promptList(reader *bufio.Reader, label string, fallback []string) []string {
|
||||
value := prompt(reader, label, strings.Join(fallback, ","))
|
||||
parts := strings.Split(value, ",")
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part != "" {
|
||||
out = append(out, part)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func promptMap(reader *bufio.Reader, label string, fallback map[string]string) map[string]string {
|
||||
pairs := make([]string, 0, len(fallback))
|
||||
for key, value := range fallback {
|
||||
pairs = append(pairs, key+"="+value)
|
||||
}
|
||||
value := prompt(reader, label, strings.Join(pairs, ","))
|
||||
result := map[string]string{}
|
||||
for _, pair := range strings.Split(value, ",") {
|
||||
parts := strings.SplitN(strings.TrimSpace(pair), "=", 2)
|
||||
if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
|
||||
result[parts[0]] = parts[1]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func promptNodes(reader *bufio.Reader, defaultNode string) []config.TalosNode {
|
||||
countValue := prompt(reader, "Number of Talos nodes", "3")
|
||||
count, err := strconv.Atoi(countValue)
|
||||
if err != nil || count < 1 {
|
||||
count = 3
|
||||
}
|
||||
result := make([]config.TalosNode, 0, count)
|
||||
for i := 0; i < count; i++ {
|
||||
name := prompt(reader, fmt.Sprintf("Node %d name", i+1), fmt.Sprintf("talos-node-%02d", i+1))
|
||||
role := prompt(reader, fmt.Sprintf("Node %d role", i+1), map[bool]string{i == 0: "controlplane", i != 0: "worker"}[true])
|
||||
vmid, _ := strconv.Atoi(prompt(reader, fmt.Sprintf("Node %d VMID", i+1), fmt.Sprintf("10%d", 50+i)))
|
||||
cores, _ := strconv.Atoi(prompt(reader, fmt.Sprintf("Node %d cores", i+1), "4"))
|
||||
memory, _ := strconv.Atoi(prompt(reader, fmt.Sprintf("Node %d memory MB", i+1), "8192"))
|
||||
diskSize := prompt(reader, fmt.Sprintf("Node %d disk size", i+1), "64G")
|
||||
proxmoxNode := prompt(reader, fmt.Sprintf("Node %d Proxmox node", i+1), defaultNode)
|
||||
tags := promptList(reader, fmt.Sprintf("Node %d tags", i+1), []string{"talos", role})
|
||||
mac := prompt(reader, fmt.Sprintf("Node %d primary MAC", i+1), "")
|
||||
cidr := prompt(reader, fmt.Sprintf("Node %d primary CIDR", i+1), "192.168.43.0/28")
|
||||
ip := prompt(reader, fmt.Sprintf("Node %d primary IP", i+1), "")
|
||||
gateway := prompt(reader, fmt.Sprintf("Node %d gateway", i+1), "192.168.43.1")
|
||||
vlanID, _ := strconv.Atoi(prompt(reader, fmt.Sprintf("Node %d VLAN ID", i+1), "43"))
|
||||
|
||||
result = append(result, config.TalosNode{
|
||||
Name: name,
|
||||
VMID: vmid,
|
||||
Role: role,
|
||||
Cores: cores,
|
||||
Memory: memory,
|
||||
DiskSize: diskSize,
|
||||
Tags: tags,
|
||||
ProxmoxNode: proxmoxNode,
|
||||
Networks: []config.TalosNetwork{{
|
||||
MACAddress: mac,
|
||||
CIDR: cidr,
|
||||
IP: ip,
|
||||
Gateway: gateway,
|
||||
VLANID: vlanID,
|
||||
}},
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
Loading…
Reference in a new issue