feat: added readme template generation & automated flux manifest generation
This commit is contained in:
parent
b75c316ea7
commit
552a4fbbc4
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
|
@ -13,7 +13,9 @@
|
||||||
"--org",
|
"--org",
|
||||||
"Pingu-Studio",
|
"Pingu-Studio",
|
||||||
"--manifests-repo",
|
"--manifests-repo",
|
||||||
"cicd-deployment-manifests"
|
"cicd-deployment-manifests-test",
|
||||||
|
"--flux-repo",
|
||||||
|
"maidn-cicd-test"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceFolder}"
|
"cwd": "${workspaceFolder}"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
321
main.go
321
main.go
|
|
@ -1,15 +1,108 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var orgName, manifestsRepoName string
|
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() {
|
func main() {
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
|
|
@ -24,16 +117,20 @@ func main() {
|
||||||
|
|
||||||
var initCmd = &cobra.Command{
|
var initCmd = &cobra.Command{
|
||||||
Use: "init",
|
Use: "init",
|
||||||
Short: "Initializes the manifests repository with namespaces and environment directories.",
|
Short: "Initializes the manifests and flux repositories with the required structure.",
|
||||||
Run: runInitRepo,
|
Run: runInitRepo,
|
||||||
}
|
}
|
||||||
|
|
||||||
initCmd.Flags().StringVar(&orgName, "org", "", "The GitHub organization (e.g., Pingu-Studio)")
|
// 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(&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("org")
|
||||||
|
initCmd.MarkFlagRequired("flux-repo")
|
||||||
|
|
||||||
repoCmd.AddCommand(initCmd)
|
repoCmd.AddCommand(initCmd)
|
||||||
rootCmd.AddCommand(repoCmd)
|
rootCmd.AddCommand(repoCmd)
|
||||||
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
@ -41,12 +138,14 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runInitRepo(cmd *cobra.Command, args []string) {
|
func runInitRepo(cmd *cobra.Command, args []string) {
|
||||||
|
// Check system dependencies
|
||||||
fmt.Println("[INFO] Checking dependencies (git and gh)...")
|
fmt.Println("[INFO] Checking dependencies (git and gh)...")
|
||||||
if !commandExists("git") || !commandExists("gh") {
|
if !commandExists("git") || !commandExists("gh") {
|
||||||
fmt.Println("[ERROR] 'git' and 'gh' must be installed and in your PATH.")
|
fmt.Println("[ERROR] 'git' and 'gh' must be installed and in your PATH.")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify GitHub authentication
|
||||||
fmt.Println("[INFO] Checking GitHub authentication...")
|
fmt.Println("[INFO] Checking GitHub authentication...")
|
||||||
if err := runCommandQuiet("gh", "auth", "status"); err != nil {
|
if err := runCommandQuiet("gh", "auth", "status"); err != nil {
|
||||||
fmt.Println("[ERROR] You are not logged into the GitHub CLI. Please run 'gh auth login'.")
|
fmt.Println("[ERROR] You are not logged into the GitHub CLI. Please run 'gh auth login'.")
|
||||||
|
|
@ -54,49 +153,164 @@ func runInitRepo(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
fmt.Println("[SUCCESS] Dependencies and authentication are OK.")
|
fmt.Println("[SUCCESS] Dependencies and authentication are OK.")
|
||||||
|
|
||||||
fullRepoName := fmt.Sprintf("%s/%s", orgName, manifestsRepoName)
|
// Initialize both repositories
|
||||||
|
initializeManifestsRepo()
|
||||||
|
initializeFluxRepo()
|
||||||
|
|
||||||
fmt.Printf("[INFO] Checking for repository '%s'...\n", fullRepoName)
|
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 {
|
if err := runCommandQuiet("gh", "repo", "view", fullRepoName); err != nil {
|
||||||
fmt.Printf("[WARNING] Repository '%s' not found. Creating it now...\n", fullRepoName)
|
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 {
|
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)
|
fmt.Printf("[ERROR] Failed to create repository: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println("[SUCCESS] Repository created.")
|
fmt.Println("[SUCCESS] Manifests repository created.")
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("[SUCCESS] Repository already exists.")
|
fmt.Println("[SUCCESS] Manifests repository already exists.")
|
||||||
}
|
}
|
||||||
|
|
||||||
tempDir, err := os.MkdirTemp("", "manifests-repo-*")
|
// 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 {
|
if err != nil {
|
||||||
fmt.Printf("[ERROR] Failed to create temporary directory: %v\n", err)
|
fmt.Printf("[ERROR] Failed to create temporary directory: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tempDir)
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
fmt.Printf("[INFO] Cloning repository into %s...\n", tempDir)
|
// Clone repository
|
||||||
|
fmt.Printf("[INFO] Cloning repository into temporary directory...\n")
|
||||||
repoURL := fmt.Sprintf("https://github.com/%s.git", fullRepoName)
|
repoURL := fmt.Sprintf("https://github.com/%s.git", fullRepoName)
|
||||||
if err := runCommand("git", "clone", repoURL, tempDir); err != nil {
|
if err := runCommand("git", "clone", repoURL, tempDir); err != nil {
|
||||||
fmt.Printf("[ERROR] Failed to clone repository: %v\n", err)
|
fmt.Printf("[ERROR] Failed to clone repository: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("[INFO] Ensuring standard directory structure and manifests exist...")
|
// Create repository structure
|
||||||
createStructureAndManifests(tempDir)
|
fmt.Println("[INFO] Creating repository structure...")
|
||||||
|
structureFunc(tempDir)
|
||||||
|
|
||||||
fmt.Println("[INFO] Committing and pushing changes if any...")
|
// Commit and push changes if any exist
|
||||||
runCommandInDir(tempDir, "git", "config", "user.name", "CICD Tool Initializer")
|
fmt.Println("[INFO] Committing and pushing changes...")
|
||||||
runCommandInDir(tempDir, "git", "config", "user.email", "actions@github.com")
|
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", ".")
|
runCommandInDir(tempDir, "git", "add", ".")
|
||||||
|
|
||||||
|
// Check if there are changes to commit
|
||||||
statusCmd := exec.Command("git", "status", "--porcelain")
|
statusCmd := exec.Command("git", "status", "--porcelain")
|
||||||
statusCmd.Dir = tempDir
|
statusCmd.Dir = tempDir
|
||||||
output, _ := statusCmd.Output()
|
output, _ := statusCmd.Output()
|
||||||
|
|
||||||
if len(output) == 0 {
|
if len(output) == 0 {
|
||||||
fmt.Println("[INFO] No changes to commit. Repository is already initialized correctly.")
|
fmt.Println("[INFO] No changes to commit. Repository is already up to date.")
|
||||||
} else {
|
} else {
|
||||||
commitMsg := "feat: Initialize repository structure with namespaces and apps"
|
// Commit and push changes
|
||||||
|
commitMsg := "feat: Initialize repository structure for CI/CD"
|
||||||
if err := runCommandInDir(tempDir, "git", "commit", "-m", commitMsg); err != nil {
|
if err := runCommandInDir(tempDir, "git", "commit", "-m", commitMsg); err != nil {
|
||||||
fmt.Printf("[ERROR] Failed to commit changes: %v\n", err)
|
fmt.Printf("[ERROR] Failed to commit changes: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
@ -105,74 +319,11 @@ func runInitRepo(cmd *cobra.Command, args []string) {
|
||||||
fmt.Printf("[ERROR] Failed to push changes: %v\n", err)
|
fmt.Printf("[ERROR] Failed to push changes: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println("[SUCCESS] Changes pushed successfully.")
|
fmt.Printf("[SUCCESS] Changes pushed to %s successfully.\n", repoName)
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to write content to a file
|
||||||
func writeFile(path, content string) {
|
func writeFile(path, content string) {
|
||||||
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
||||||
fmt.Printf("[ERROR] Failed to write file %s: %v\n", path, err)
|
fmt.Printf("[ERROR] Failed to write file %s: %v\n", path, err)
|
||||||
|
|
@ -180,11 +331,13 @@ func writeFile(path, content string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to check if a command exists in PATH
|
||||||
func commandExists(cmd string) bool {
|
func commandExists(cmd string) bool {
|
||||||
_, err := exec.LookPath(cmd)
|
_, err := exec.LookPath(cmd)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to run a command with output visible
|
||||||
func runCommand(name string, args ...string) error {
|
func runCommand(name string, args ...string) error {
|
||||||
cmd := exec.Command(name, args...)
|
cmd := exec.Command(name, args...)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
|
|
@ -192,6 +345,7 @@ func runCommand(name string, args ...string) error {
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to run a command quietly (no stdout)
|
||||||
func runCommandQuiet(name string, args ...string) error {
|
func runCommandQuiet(name string, args ...string) error {
|
||||||
cmd := exec.Command(name, args...)
|
cmd := exec.Command(name, args...)
|
||||||
cmd.Stdout = nil
|
cmd.Stdout = nil
|
||||||
|
|
@ -199,6 +353,7 @@ func runCommandQuiet(name string, args ...string) error {
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to run a command in a specific directory
|
||||||
func runCommandInDir(dir string, name string, args ...string) error {
|
func runCommandInDir(dir string, name string, args ...string) error {
|
||||||
cmd := exec.Command(name, args...)
|
cmd := exec.Command(name, args...)
|
||||||
cmd.Dir = dir
|
cmd.Dir = dir
|
||||||
|
|
|
||||||
72
templates/flux.md.tmpl
Normal file
72
templates/flux.md.tmpl
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
# %s
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This repository contains the core configuration for Flux CD, which manages our Kubernetes clusters using a GitOps approach. It defines the sources of truth for our applications and the automation rules for deploying them.
|
||||||
|
|
||||||
|
## Repository Structure
|
||||||
|
|
||||||
|
The structure is designed to be minimal, with all cluster-specific configurations located in a dedicated directory.
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── clusters/
|
||||||
|
│ └── dev-cluster/
|
||||||
|
│ └── cicd-manifests-repo.yaml # Flux resource definitions
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Details
|
||||||
|
|
||||||
|
The **/clusters/dev-cluster** directory contains the primary Flux CD custom resources that orchestrate the deployment pipeline:
|
||||||
|
|
||||||
|
* **`HelmRepository`**: Configures access to the OCI-based Helm chart registry where application charts are stored.
|
||||||
|
* **`GitRepository`**: Defines the source Git repository (`cicd-deployment-manifests`) that holds the application deployment manifests.
|
||||||
|
* **`Kustomization`**: Specifies how the manifests from the source repository should be applied to the cluster. Multiple `Kustomization` resources are used to manage different environments and enforce deployment dependencies (e.g., ensuring namespaces exist before applications are deployed).
|
||||||
|
|
||||||
|
## Operational Workflow
|
||||||
|
|
||||||
|
1. **Source Monitoring**: Flux continuously monitors the `cicd-deployment-manifests` repository for any new commits to the `main` branch.
|
||||||
|
2. **Automated Reconciliation**: Upon detecting changes, Flux automatically fetches the latest manifests and applies them to the cluster.
|
||||||
|
3. **Dependency Management**: The `dependsOn` attribute in the `Kustomization` resources ensures that deployments occur in the correct order.
|
||||||
|
4. **Pruning**: Flux is configured to prune resources. If a manifest is deleted from the Git repository, Flux will automatically delete the corresponding resource from the Kubernetes cluster.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
For Flux to operate correctly, the Kubernetes cluster must be bootstrapped with the following secrets in the `flux-system` namespace.
|
||||||
|
|
||||||
|
### Required Secrets
|
||||||
|
|
||||||
|
**1. GitHub Authentication (`github-auth`)**
|
||||||
|
|
||||||
|
A secret containing a GitHub Personal Access Token (PAT) with repository access.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: github-auth
|
||||||
|
namespace: flux-system
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
username: <BASE64_ENCODED_GITHUB_USERNAME>
|
||||||
|
password: <BASE64_ENCODED_GITHUB_PAT>
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Container Registry Authentication (`ghcr-auth`)**
|
||||||
|
|
||||||
|
A Docker registry secret for pulling images from the GitHub Container Registry (GHCR).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: ghcr-auth
|
||||||
|
namespace: flux-system
|
||||||
|
type: kubernetes.io/dockerconfigjson
|
||||||
|
data:
|
||||||
|
.dockerconfigjson: <BASE64_ENCODED_DOCKER_CONFIG_JSON>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
*This repository is automatically generated and managed by internal CI/CD tooling.*
|
||||||
45
templates/manifests.md.tmpl
Normal file
45
templates/manifests.md.tmpl
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# %s
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This repository serves as the centralized source for all Kubernetes deployment manifests. It is managed by Flux CD, adhering to GitOps principles to ensure that the state of the Kubernetes clusters reflects the configuration defined here.
|
||||||
|
|
||||||
|
## Repository Structure
|
||||||
|
|
||||||
|
The repository is organized to support multiple environments and to provide a clear separation of concerns.
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── apps/
|
||||||
|
│ ├── previews/ # Manifests for dynamic preview environments
|
||||||
|
│ ├── staging/ # Manifests for the staging environment
|
||||||
|
│ └── production/ # Manifests for the production environment
|
||||||
|
├── namespaces/ # Core Kubernetes namespace definitions
|
||||||
|
│ ├── staging.yaml
|
||||||
|
│ ├── production.yaml
|
||||||
|
│ └── kustomization.yaml
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Directories
|
||||||
|
|
||||||
|
* **/namespaces**: Contains the YAML definitions for Kubernetes namespaces used across all environments. This ensures that foundational resources are managed consistently.
|
||||||
|
* **/apps**: Holds the application-specific deployment manifests, categorized by environment.
|
||||||
|
|
||||||
|
## Environment Management
|
||||||
|
|
||||||
|
- **Production**: The live environment serving end-users. Changes to this directory directly impact production services.
|
||||||
|
- **Staging**: A pre-production environment that mirrors production. It is used for final testing and validation before deploying changes to production.
|
||||||
|
- **Previews**: A dynamic environment for deploying feature branches or pull requests. These are short-lived and used for testing and review purposes.
|
||||||
|
|
||||||
|
## Deployment Workflow
|
||||||
|
|
||||||
|
Changes are deployed automatically by Flux CD, which continuously monitors this repository. The standard workflow is as follows:
|
||||||
|
|
||||||
|
1. **Commit Manifests**: A developer or an automated process commits new or updated Kubernetes manifests to the appropriate directory (e.g., `apps/staging`).
|
||||||
|
2. **Push to `main`**: The changes are pushed to the `main` branch.
|
||||||
|
3. **Flux Synchronization**: Flux detects the changes in the repository.
|
||||||
|
4. **Cluster Reconciliation**: Flux applies the manifest changes to the corresponding Kubernetes cluster, ensuring the cluster's state matches the repository's configuration.
|
||||||
|
|
||||||
|
---
|
||||||
|
*This repository is automatically generated and managed by internal CI/CD tooling.*
|
||||||
Loading…
Reference in a new issue