maidn-cli/main.go
2025-08-21 23:58:47 +02:00

209 lines
6.1 KiB
Go

package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/spf13/cobra"
)
var orgName, manifestsRepoName string
func main() {
var rootCmd = &cobra.Command{
Use: "cicd-tool",
Short: "A CLI tool to manage CI/CD setup for applications.",
}
var repoCmd = &cobra.Command{
Use: "repo",
Short: "Manage CI/CD repositories.",
}
var initCmd = &cobra.Command{
Use: "init",
Short: "Initializes the manifests repository with namespaces and environment directories.",
Run: runInitRepo,
}
initCmd.Flags().StringVar(&orgName, "org", "", "The GitHub organization (e.g., Pingu-Studio)")
initCmd.Flags().StringVar(&manifestsRepoName, "manifests-repo", "cicd-deployment-manifests", "The name of the manifests repository")
initCmd.MarkFlagRequired("org")
repoCmd.AddCommand(initCmd)
rootCmd.AddCommand(repoCmd)
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func runInitRepo(cmd *cobra.Command, args []string) {
fmt.Println("[INFO] Checking dependencies (git and gh)...")
if !commandExists("git") || !commandExists("gh") {
fmt.Println("[ERROR] 'git' and 'gh' must be installed and in your PATH.")
os.Exit(1)
}
fmt.Println("[INFO] Checking GitHub authentication...")
if err := runCommandQuiet("gh", "auth", "status"); err != nil {
fmt.Println("[ERROR] You are not logged into the GitHub CLI. Please run 'gh auth login'.")
os.Exit(1)
}
fmt.Println("[SUCCESS] Dependencies and authentication are OK.")
fullRepoName := fmt.Sprintf("%s/%s", orgName, manifestsRepoName)
fmt.Printf("[INFO] Checking for repository '%s'...\n", fullRepoName)
if err := runCommandQuiet("gh", "repo", "view", fullRepoName); err != nil {
fmt.Printf("[WARNING] Repository '%s' not found. Creating it now...\n", fullRepoName)
if err := runCommand("gh", "repo", "create", fullRepoName, "--private", "--description", "Centralized deployment manifests for Flux CD"); err != nil {
fmt.Printf("[ERROR] Failed to create repository: %v\n", err)
os.Exit(1)
}
fmt.Println("[SUCCESS] Repository created.")
} else {
fmt.Println("[SUCCESS] Repository already exists.")
}
tempDir, err := os.MkdirTemp("", "manifests-repo-*")
if err != nil {
fmt.Printf("[ERROR] Failed to create temporary directory: %v\n", err)
os.Exit(1)
}
defer os.RemoveAll(tempDir)
fmt.Printf("[INFO] Cloning repository into %s...\n", tempDir)
repoURL := fmt.Sprintf("https://github.com/%s.git", fullRepoName)
if err := runCommand("git", "clone", repoURL, tempDir); err != nil {
fmt.Printf("[ERROR] Failed to clone repository: %v\n", err)
os.Exit(1)
}
fmt.Println("[INFO] Ensuring standard directory structure and manifests exist...")
createStructureAndManifests(tempDir)
fmt.Println("[INFO] Committing and pushing changes if any...")
runCommandInDir(tempDir, "git", "config", "user.name", "CICD Tool Initializer")
runCommandInDir(tempDir, "git", "config", "user.email", "actions@github.com")
runCommandInDir(tempDir, "git", "add", ".")
statusCmd := exec.Command("git", "status", "--porcelain")
statusCmd.Dir = tempDir
output, _ := statusCmd.Output()
if len(output) == 0 {
fmt.Println("[INFO] No changes to commit. Repository is already initialized correctly.")
} else {
commitMsg := "feat: Initialize repository structure with namespaces and apps"
if err := runCommandInDir(tempDir, "git", "commit", "-m", commitMsg); err != nil {
fmt.Printf("[ERROR] Failed to commit changes: %v\n", err)
os.Exit(1)
}
if err := runCommandInDir(tempDir, "git", "push", "origin", "main"); err != nil {
fmt.Printf("[ERROR] Failed to push changes: %v\n", err)
os.Exit(1)
}
fmt.Println("[SUCCESS] Changes pushed successfully.")
}
fmt.Println("\n🎉 Onboarding complete! 🎉")
}
func createStructureAndManifests(baseDir string) {
environments := []string{"previews", "staging", "production"}
placeholderContent := `apiVersion: v1
kind: ConfigMap
metadata:
name: placeholder
data:
info: "This file ensures Kustomize globs do not fail in an empty environment."
`
for _, env := range environments {
dirPath := filepath.Join(baseDir, "apps", env)
if err := os.MkdirAll(dirPath, 0755); err != nil {
fmt.Printf("[ERROR] Failed to create directory %s: %v\n", dirPath, err)
os.Exit(1)
}
writeFile(filepath.Join(dirPath, "placeholder.yaml"), placeholderContent)
}
appsKustomizationContent := `apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- previews/*.yaml
- staging/*.yaml
- production/*.yaml
`
writeFile(filepath.Join(baseDir, "apps", "kustomization.yaml"), appsKustomizationContent)
namespacesDir := filepath.Join(baseDir, "namespaces")
if err := os.MkdirAll(namespacesDir, 0755); err != nil {
fmt.Printf("[ERROR] Failed to create directory %s: %v\n", namespacesDir, err)
os.Exit(1)
}
for _, ns := range environments {
namespaceContent := fmt.Sprintf(`apiVersion: v1
kind: Namespace
metadata:
name: %s
`, ns)
writeFile(filepath.Join(namespacesDir, ns+".yaml"), namespaceContent)
}
kustomizeNamespaces := `apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- previews.yaml
- staging.yaml
- production.yaml
`
writeFile(filepath.Join(namespacesDir, "kustomization.yaml"), kustomizeNamespaces)
kustomizeRoot := `apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./namespaces
- ./apps
`
writeFile(filepath.Join(baseDir, "kustomization.yaml"), kustomizeRoot)
}
func writeFile(path, content string) {
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
fmt.Printf("[ERROR] Failed to write file %s: %v\n", path, err)
os.Exit(1)
}
}
func commandExists(cmd string) bool {
_, err := exec.LookPath(cmd)
return err == nil
}
func runCommand(name string, args ...string) error {
cmd := exec.Command(name, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func runCommandQuiet(name string, args ...string) error {
cmd := exec.Command(name, args...)
cmd.Stdout = nil
cmd.Stderr = os.Stderr
return cmd.Run()
}
func runCommandInDir(dir string, name string, args ...string) error {
cmd := exec.Command(name, args...)
cmd.Dir = dir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}