maidn-cli/internal/github/repository.go

247 lines
7 KiB
Go

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:
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) 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)
}
}