package main import ( _ "embed" "fmt" "os" "os/exec" "path/filepath" "strings" "github.com/spf13/cobra" ) var orgName, manifestsRepoName, fluxRepoName string //go:embed templates/manifests.md.tmpl var manifestsReadmeTmpl string //go:embed templates/flux.md.tmpl var fluxReadmeTmpl string const fluxConfigTmpl = `--- apiVersion: source.toolkit.fluxcd.io/v1beta2 kind: HelmRepository metadata: name: ghcr-charts namespace: flux-system spec: interval: 5m type: oci url: oci://ghcr.io/%s secretRef: name: ghcr-auth --- apiVersion: source.toolkit.fluxcd.io/v1 kind: GitRepository metadata: name: cicd-deployment-manifests namespace: flux-system spec: interval: 1m0s url: https://github.com/%s/%s ref: branch: main secretRef: name: github-auth --- apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: namespaces namespace: flux-system spec: interval: 10m path: ./namespaces prune: true sourceRef: kind: GitRepository name: cicd-deployment-manifests --- apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: preview-apps namespace: flux-system spec: dependsOn: - name: namespaces interval: 2m0s path: ./apps/previews prune: true sourceRef: kind: GitRepository name: cicd-deployment-manifests --- apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: staging-apps namespace: flux-system spec: dependsOn: - name: namespaces interval: 2m0s path: ./apps/staging prune: true sourceRef: kind: GitRepository name: cicd-deployment-manifests --- apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: production-apps namespace: flux-system spec: dependsOn: - name: namespaces interval: 2m0s path: ./apps/production prune: true sourceRef: kind: GitRepository name: cicd-deployment-manifests ` 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 and flux repositories with the required structure.", Run: runInitRepo, } // Define command flags initCmd.Flags().StringVar(&orgName, "org", "", "The GitHub organization (e.g., Free-Maidn)") initCmd.Flags().StringVar(&manifestsRepoName, "manifests-repo", "cicd-deployment-manifests", "The name of the manifests repository") initCmd.Flags().StringVar(&fluxRepoName, "flux-repo", "", "The name of the flux repository") initCmd.MarkFlagRequired("org") initCmd.MarkFlagRequired("flux-repo") 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) { // Check system dependencies 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) } // Verify GitHub authentication 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.") // Initialize both repositories initializeManifestsRepo() initializeFluxRepo() fmt.Println("\nšŸŽ‰ Onboarding complete! šŸŽ‰") fmt.Printf("āœ… Manifests repository: https://github.com/%s/%s\n", orgName, manifestsRepoName) fmt.Printf("āœ… Flux repository: https://github.com/%s/%s\n", orgName, fluxRepoName) } func initializeManifestsRepo() { fullRepoName := fmt.Sprintf("%s/%s", orgName, manifestsRepoName) fmt.Printf("\n[INFO] Setting up manifests repository '%s'...\n", fullRepoName) // Check if repository exists, create if not if err := runCommandQuiet("gh", "repo", "view", fullRepoName); err != nil { fmt.Printf("[INFO] Creating repository '%s'...\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] Manifests repository created.") } else { fmt.Println("[SUCCESS] Manifests repository already exists.") } // Clone and setup repository structure setupRepository(fullRepoName, createManifestsStructure) } func initializeFluxRepo() { fullRepoName := fmt.Sprintf("%s/%s", orgName, fluxRepoName) fmt.Printf("\n[INFO] Setting up flux repository '%s'...\n", fullRepoName) // Check if repository exists, create if not if err := runCommandQuiet("gh", "repo", "view", fullRepoName); err != nil { fmt.Printf("[INFO] Creating repository '%s'...\n", fullRepoName) if err := runCommand("gh", "repo", "create", fullRepoName, "--private", "--description", "Flux CD cluster configurations"); err != nil { fmt.Printf("[ERROR] Failed to create repository: %v\n", err) os.Exit(1) } fmt.Println("[SUCCESS] Flux repository created.") } else { fmt.Println("[SUCCESS] Flux repository already exists.") } // Clone and setup repository structure setupRepository(fullRepoName, createFluxStructure) } func setupRepository(fullRepoName string, structureFunc func(string)) { // Create temporary directory for cloning tempDir, err := os.MkdirTemp("", "repo-setup-*") if err != nil { fmt.Printf("[ERROR] Failed to create temporary directory: %v\n", err) os.Exit(1) } defer os.RemoveAll(tempDir) // Clone repository fmt.Printf("[INFO] Cloning repository into temporary directory...\n") 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) } // Create repository structure fmt.Println("[INFO] Creating repository structure...") structureFunc(tempDir) // Commit and push changes if any exist fmt.Println("[INFO] Committing and pushing changes...") commitAndPush(tempDir, fullRepoName) } func createManifestsStructure(baseDir string) { // Create README for manifests repository manifestsReadme := fmt.Sprintf(manifestsReadmeTmpl, manifestsRepoName) writeFile(filepath.Join(baseDir, "README.md"), manifestsReadme) // Create namespaces directory and files namespacesDir := filepath.Join(baseDir, "namespaces") if err := os.MkdirAll(namespacesDir, 0755); err != nil { fmt.Printf("[ERROR] Failed to create namespaces directory: %v\n", err) os.Exit(1) } // Create staging namespace stagingNamespace := `apiVersion: v1 kind: Namespace metadata: name: staging ` writeFile(filepath.Join(namespacesDir, "staging.yaml"), stagingNamespace) // Create production namespace productionNamespace := `apiVersion: v1 kind: Namespace metadata: name: production ` writeFile(filepath.Join(namespacesDir, "production.yaml"), productionNamespace) // Create namespaces kustomization namespacesKustomization := `apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - staging.yaml - production.yaml ` writeFile(filepath.Join(namespacesDir, "kustomization.yaml"), namespacesKustomization) // Create apps directory structure with .gitkeep files environments := []string{"staging", "production", "previews"} 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) } // Create .gitkeep to ensure empty directories are tracked writeFile(filepath.Join(dirPath, ".gitkeep"), "") } } func createFluxStructure(baseDir string) { // Create README for flux repository fluxReadme := fmt.Sprintf(fluxReadmeTmpl, fluxRepoName) writeFile(filepath.Join(baseDir, "README.md"), fluxReadme) // Create clusters/dev-cluster directory clusterDir := filepath.Join(baseDir, "clusters", "dev-cluster") if err := os.MkdirAll(clusterDir, 0755); err != nil { fmt.Printf("[ERROR] Failed to create cluster directory: %v\n", err) os.Exit(1) } // Create the flux configuration file with dynamic organization name fluxConfig := fmt.Sprintf(fluxConfigTmpl, strings.ToLower(orgName), orgName, manifestsRepoName) writeFile(filepath.Join(clusterDir, "cicd-manifests-repo.yaml"), fluxConfig) } func commitAndPush(tempDir, repoName string) { // Configure git user for the commit runCommandInDir(tempDir, "git", "config", "user.name", "Maidn") runCommandInDir(tempDir, "git", "config", "user.email", "maidn@free-maidn.com") runCommandInDir(tempDir, "git", "add", ".") // Check if there are changes to commit 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 up to date.") } else { // Commit and push changes commitMsg := "feat: Initialize repository structure for CI/CD" 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.Printf("[SUCCESS] Changes pushed to %s successfully.\n", repoName) } } // Helper function to write content to a file 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) } } // Helper function to check if a command exists in PATH func commandExists(cmd string) bool { _, err := exec.LookPath(cmd) return err == nil } // Helper function to run a command with output visible func runCommand(name string, args ...string) error { cmd := exec.Command(name, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() } // Helper function to run a command quietly (no stdout) func runCommandQuiet(name string, args ...string) error { cmd := exec.Command(name, args...) cmd.Stdout = nil cmd.Stderr = os.Stderr return cmd.Run() } // Helper function to run a command in a specific directory 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() }