package github import ( "fmt" "os" "os/exec" "path/filepath" "strings" "github.com/Pingu-Studio/MaidnCLI/internal/assets" "github.com/Pingu-Studio/MaidnCLI/internal/utils" ) 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: dependsOn: - name: vault-instance - name: democratic-csi - name: metallb-config 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 ` // RepoManager holds the state for repository operations type RepoManager struct { OrgName string ManifestsRepoName string FluxRepoName string } // NewRepoManager creates a new instance of RepoManager func NewRepoManager(org, manifestsRepo, fluxRepo string) *RepoManager { return &RepoManager{ OrgName: org, ManifestsRepoName: manifestsRepo, FluxRepoName: fluxRepo, } } // InitializeManifestsRepo sets up the manifests repository func (rm *RepoManager) InitializeManifestsRepo() { fullRepoName := fmt.Sprintf("%s/%s", rm.OrgName, rm.ManifestsRepoName) fmt.Printf("\n[INFO] Setting up manifests repository '%s'...\n", fullRepoName) if err := utils.RunCommandQuiet("gh", "repo", "view", fullRepoName); err != nil { fmt.Printf("[INFO] Creating repository '%s'...\n", fullRepoName) if err := utils.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.") } rm.setupRepository(fullRepoName, rm.createManifestsStructure) } // InitializeFluxRepo sets up the flux repository func (rm *RepoManager) InitializeFluxRepo() { fullRepoName := fmt.Sprintf("%s/%s", rm.OrgName, rm.FluxRepoName) fmt.Printf("\n[INFO] Setting up flux repository '%s'...\n", fullRepoName) if err := utils.RunCommandQuiet("gh", "repo", "view", fullRepoName); err != nil { fmt.Printf("[INFO] Creating repository '%s'...\n", fullRepoName) if err := utils.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.") } 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 { fmt.Printf("[ERROR] Failed to create temporary directory: %v\n", err) os.Exit(1) } defer os.RemoveAll(tempDir) fmt.Printf("[INFO] Cloning repository into temporary directory...\n") repoURL := fmt.Sprintf("https://github.com/%s.git", fullRepoName) if err := utils.RunCommand("git", "clone", repoURL, tempDir); err != nil { fmt.Printf("[ERROR] Failed to clone repository: %v\n", err) os.Exit(1) } fmt.Println("[INFO] Creating repository structure...") structureFunc(tempDir) fmt.Println("[INFO] Committing and pushing changes...") commitAndPush(tempDir, fullRepoName) } func (rm *RepoManager) createManifestsStructure(baseDir string) { manifestsReadme := fmt.Sprintf(assets.ManifestsReadmeTmpl, rm.ManifestsRepoName) utils.WriteFile(filepath.Join(baseDir, "README.md"), manifestsReadme) namespacesDir := filepath.Join(baseDir, "namespaces") os.MkdirAll(namespacesDir, 0755) stagingNamespace := `apiVersion: v1 kind: Namespace metadata: name: staging ` utils.WriteFile(filepath.Join(namespacesDir, "staging.yaml"), stagingNamespace) productionNamespace := `apiVersion: v1 kind: Namespace metadata: name: production ` utils.WriteFile(filepath.Join(namespacesDir, "production.yaml"), productionNamespace) namespacesKustomization := `apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - staging.yaml - production.yaml ` utils.WriteFile(filepath.Join(namespacesDir, "kustomization.yaml"), namespacesKustomization) environments := []string{"staging", "production", "previews"} for _, env := range environments { dirPath := filepath.Join(baseDir, "apps", env) os.MkdirAll(dirPath, 0755) utils.WriteFile(filepath.Join(dirPath, ".gitkeep"), "") } } func (rm *RepoManager) createFluxStructure(baseDir string) { fluxReadme := fmt.Sprintf(assets.FluxReadmeTmpl, rm.FluxRepoName) utils.WriteFile(filepath.Join(baseDir, "README.md"), fluxReadme) clusterDir := filepath.Join(baseDir, "clusters", "dev-cluster") os.MkdirAll(clusterDir, 0755) fluxConfig := fmt.Sprintf(fluxConfigTmpl, strings.ToLower(rm.OrgName), rm.OrgName, rm.ManifestsRepoName) utils.WriteFile(filepath.Join(clusterDir, "cicd-manifests-repo.yaml"), fluxConfig) } func commitAndPush(tempDir, repoName string) { utils.RunCommandInDir(tempDir, "git", "config", "user.name", "Maidn") utils.RunCommandInDir(tempDir, "git", "config", "user.email", "maidn@free-maidn.com") utils.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 up to date.") } else { commitMsg := "feat: Initialize repository structure for CI/CD" if err := utils.RunCommandInDir(tempDir, "git", "commit", "-m", commitMsg); err != nil { fmt.Printf("[ERROR] Failed to commit changes: %v\n", err) os.Exit(1) } if err := utils.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) } }