feat: added vault configurator
This commit is contained in:
parent
552a4fbbc4
commit
c1e2312b41
13
.vscode/launch.json
vendored
13
.vscode/launch.json
vendored
|
|
@ -18,6 +18,19 @@
|
||||||
"maidn-cicd-test"
|
"maidn-cicd-test"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceFolder}"
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Run and Debug CICD Tool docker secret creation",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "auto",
|
||||||
|
"program": "${workspaceFolder}",
|
||||||
|
"args": [
|
||||||
|
"vault",
|
||||||
|
"create-docker-secret"
|
||||||
|
],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"console": "integratedTerminal"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
11
go.mod
11
go.mod
|
|
@ -3,7 +3,12 @@ module github.com/Pingu-Studio/MaidnCLI
|
||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/spf13/cobra v1.9.1 // indirect
|
golang.org/x/term v0.34.0
|
||||||
github.com/spf13/pflag v1.0.7 // indirect
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.7 // indirect
|
||||||
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -7,5 +7,9 @@ github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wx
|
||||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||||
|
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
||||||
304
main.go
304
main.go
|
|
@ -4,9 +4,6 @@ import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
@ -19,91 +16,6 @@ var manifestsReadmeTmpl string
|
||||||
//go:embed templates/flux.md.tmpl
|
//go:embed templates/flux.md.tmpl
|
||||||
var fluxReadmeTmpl string
|
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{
|
||||||
Use: "cicd-tool",
|
Use: "cicd-tool",
|
||||||
|
|
@ -115,21 +27,35 @@ func main() {
|
||||||
Short: "Manage CI/CD repositories.",
|
Short: "Manage CI/CD repositories.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var vaultCmd = &cobra.Command{
|
||||||
|
Use: "vault",
|
||||||
|
Short: "Manage Vault secrets for CI/CD.",
|
||||||
|
}
|
||||||
|
|
||||||
var initCmd = &cobra.Command{
|
var initCmd = &cobra.Command{
|
||||||
Use: "init",
|
Use: "init",
|
||||||
Short: "Initializes the manifests and flux repositories with the required structure.",
|
Short: "Initializes the manifests and flux repositories with the required structure.",
|
||||||
Run: runInitRepo,
|
Run: runInitRepo,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define command flags
|
var createSecretCmd = &cobra.Command{
|
||||||
|
Use: "create-docker-secret",
|
||||||
|
Short: "Creates a Docker registry secret in Vault for GHCR authentication.",
|
||||||
|
Run: runCreateDockerSecret,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define command flags for repo init
|
||||||
initCmd.Flags().StringVar(&orgName, "org", "", "The GitHub organization (e.g., Free-Maidn)")
|
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.Flags().StringVar(&fluxRepoName, "flux-repo", "", "The name of the flux repository")
|
||||||
initCmd.MarkFlagRequired("org")
|
initCmd.MarkFlagRequired("org")
|
||||||
initCmd.MarkFlagRequired("flux-repo")
|
initCmd.MarkFlagRequired("flux-repo")
|
||||||
|
|
||||||
|
// Add commands to their parent commands
|
||||||
repoCmd.AddCommand(initCmd)
|
repoCmd.AddCommand(initCmd)
|
||||||
|
vaultCmd.AddCommand(createSecretCmd)
|
||||||
rootCmd.AddCommand(repoCmd)
|
rootCmd.AddCommand(repoCmd)
|
||||||
|
rootCmd.AddCommand(vaultCmd)
|
||||||
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|
@ -162,202 +88,10 @@ func runInitRepo(cmd *cobra.Command, args []string) {
|
||||||
fmt.Printf("✅ Flux repository: https://github.com/%s/%s\n", orgName, fluxRepoName)
|
fmt.Printf("✅ Flux repository: https://github.com/%s/%s\n", orgName, fluxRepoName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initializeManifestsRepo() {
|
func runCreateDockerSecret(cmd *cobra.Command, args []string) {
|
||||||
fullRepoName := fmt.Sprintf("%s/%s", orgName, manifestsRepoName)
|
if err := createDockerRegistrySecret(); err != nil {
|
||||||
fmt.Printf("\n[INFO] Setting up manifests repository '%s'...\n", fullRepoName)
|
fmt.Printf("[ERROR] Failed to create Docker registry secret: %v\n", err)
|
||||||
|
|
||||||
// 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)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println("[SUCCESS] Manifests repository created.")
|
fmt.Println("[SUCCESS] Docker registry secret created successfully in Vault!")
|
||||||
} 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()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
255
repository.go
Normal file
255
repository.go
Normal file
|
|
@ -0,0 +1,255 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
46
utils.go
Normal file
46
utils.go
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
266
vault.go
Normal file
266
vault.go
Normal file
|
|
@ -0,0 +1,266 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DockerConfig represents the structure of a Docker config.json
|
||||||
|
type DockerConfig struct {
|
||||||
|
Auths map[string]DockerAuth `json:"auths"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DockerAuth represents the authentication info for a Docker registry
|
||||||
|
type DockerAuth struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Auth string `json:"auth"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VaultSecret represents the structure for storing in Vault
|
||||||
|
type VaultSecret struct {
|
||||||
|
DockerConfigJSON string `json:".dockerconfigjson"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// createDockerRegistrySecret creates a Docker registry secret in Vault
|
||||||
|
func createDockerRegistrySecret() error {
|
||||||
|
// Check dependencies
|
||||||
|
fmt.Println("[INFO] Checking dependencies (kubectl and vault)...")
|
||||||
|
if !commandExists("kubectl") {
|
||||||
|
return fmt.Errorf("'kubectl' must be installed and in your PATH")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check kubectl context
|
||||||
|
fmt.Println("[INFO] Checking Kubernetes context...")
|
||||||
|
if err := runCommandQuiet("kubectl", "get", "nodes"); err != nil {
|
||||||
|
return fmt.Errorf("unable to connect to Kubernetes cluster. Please check your kubectl configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if vault namespace exists
|
||||||
|
fmt.Println("[INFO] Checking if 'vault' namespace exists...")
|
||||||
|
if err := runCommandQuiet("kubectl", "get", "namespace", "vault"); err != nil {
|
||||||
|
fmt.Println("[WARNING] 'vault' namespace does not exist. Please ensure Vault is properly installed in your cluster.")
|
||||||
|
return fmt.Errorf("vault namespace not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user input
|
||||||
|
username, token, err := getUserInput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get user input: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Docker config
|
||||||
|
dockerConfig, err := createDockerConfig(username, token)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create Docker config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store in Vault
|
||||||
|
fmt.Println("[INFO] Storing secret in Vault...")
|
||||||
|
if err := storeInVault(dockerConfig); err != nil {
|
||||||
|
return fmt.Errorf("failed to store secret in Vault: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUserInput prompts the user for their GitHub username and token
|
||||||
|
func getUserInput() (string, string, error) {
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
|
// Get username
|
||||||
|
fmt.Print("Enter your GitHub username: ")
|
||||||
|
username, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
username = strings.TrimSpace(username)
|
||||||
|
|
||||||
|
if username == "" {
|
||||||
|
return "", "", fmt.Errorf("username cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get token (hidden input)
|
||||||
|
fmt.Print("Enter your GitHub personal access token: ")
|
||||||
|
tokenBytes, err := term.ReadPassword(int(syscall.Stdin))
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
fmt.Println() // Print newline after hidden input
|
||||||
|
|
||||||
|
token := string(tokenBytes)
|
||||||
|
if token == "" {
|
||||||
|
return "", "", fmt.Errorf("token cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate token format (GitHub tokens start with ghp_, gho_, ghu_, ghs_, or ghr_)
|
||||||
|
if !strings.HasPrefix(token, "ghp_") && !strings.HasPrefix(token, "gho_") &&
|
||||||
|
!strings.HasPrefix(token, "ghu_") && !strings.HasPrefix(token, "ghs_") &&
|
||||||
|
!strings.HasPrefix(token, "ghr_") {
|
||||||
|
fmt.Println("[WARNING] The token doesn't appear to be a GitHub personal access token (should start with ghp_, gho_, ghu_, ghs_, or ghr_)")
|
||||||
|
fmt.Print("Do you want to continue anyway? (y/N): ")
|
||||||
|
|
||||||
|
confirm, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
confirm = strings.TrimSpace(strings.ToLower(confirm))
|
||||||
|
|
||||||
|
if confirm != "y" && confirm != "yes" {
|
||||||
|
return "", "", fmt.Errorf("operation cancelled by user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return username, token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createDockerConfig creates a Docker config JSON structure
|
||||||
|
func createDockerConfig(username, token string) (string, error) {
|
||||||
|
// Create base64 encoded auth string
|
||||||
|
auth := base64.StdEncoding.EncodeToString([]byte(username + ":" + token))
|
||||||
|
|
||||||
|
// Create Docker config structure
|
||||||
|
config := DockerConfig{
|
||||||
|
Auths: map[string]DockerAuth{
|
||||||
|
"ghcr.io": {
|
||||||
|
Username: username,
|
||||||
|
Password: token,
|
||||||
|
Auth: auth,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal to JSON
|
||||||
|
configJSON, err := json.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(configJSON), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// storeInVault stores the Docker config in Vault KV v2
|
||||||
|
func storeInVault(dockerConfigJSON string) error {
|
||||||
|
fmt.Println("[INFO] Setting up Vault connection...")
|
||||||
|
|
||||||
|
// Get the vault pod name
|
||||||
|
fmt.Println("[INFO] Finding Vault pod...")
|
||||||
|
vaultPod, err := getVaultPodName()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to find Vault pod: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[INFO] Using Vault pod: %s\n", vaultPod)
|
||||||
|
|
||||||
|
// Get the root token
|
||||||
|
fmt.Println("[INFO] Retrieving Vault root token...")
|
||||||
|
rootToken, err := getVaultRootToken()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get Vault root token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set environment and authenticate
|
||||||
|
fmt.Println("[INFO] Setting up Vault environment...")
|
||||||
|
envSetupCmd := fmt.Sprintf("export VAULT_SKIP_VERIFY=true && export VAULT_ADDR='https://127.0.0.1:8200' && export VAULT_TOKEN='%s'", rootToken)
|
||||||
|
|
||||||
|
// Test authentication
|
||||||
|
authTestCmd := fmt.Sprintf("%s && vault token lookup", envSetupCmd)
|
||||||
|
if err := runCommand("kubectl", "exec", "-n", "vault", vaultPod, "--", "sh", "-c", authTestCmd); err != nil {
|
||||||
|
return fmt.Errorf("failed to validate Vault token: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Println("[SUCCESS] Vault token is valid")
|
||||||
|
|
||||||
|
// Store the secret using kubectl exec to vault pod
|
||||||
|
fmt.Println("[INFO] Storing secret in Vault KV store...")
|
||||||
|
|
||||||
|
// Use kubectl exec to run vault command inside the pod with proper env vars and token
|
||||||
|
vaultCmd := fmt.Sprintf("%s && vault kv put secret/ghcr-auth .dockerconfigjson='%s'", envSetupCmd, dockerConfigJSON)
|
||||||
|
|
||||||
|
if err := runCommand("kubectl", "exec", "-n", "vault", vaultPod, "--", "sh", "-c", vaultCmd); err != nil {
|
||||||
|
return fmt.Errorf("failed to store secret in Vault: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the secret was stored
|
||||||
|
fmt.Println("[INFO] Verifying secret storage...")
|
||||||
|
verifyCmd := fmt.Sprintf("%s && vault kv get secret/ghcr-auth", envSetupCmd)
|
||||||
|
if err := runCommand("kubectl", "exec", "-n", "vault", vaultPod, "--", "sh", "-c", verifyCmd); err != nil {
|
||||||
|
fmt.Println("[WARNING] Could not verify secret storage, but it may have been created successfully")
|
||||||
|
fmt.Printf("[INFO] You can manually verify with: kubectl exec -n vault %s -- sh -c \"%s\"\n", vaultPod, verifyCmd)
|
||||||
|
} else {
|
||||||
|
fmt.Println("[SUCCESS] Secret stored and verified in Vault!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getVaultPodName gets the name of a running Vault pod
|
||||||
|
func getVaultPodName() (string, error) {
|
||||||
|
// Try to get vault-0 first (common StatefulSet naming)
|
||||||
|
if err := runCommandQuiet("kubectl", "get", "pod", "vault-0", "-n", "vault"); err == nil {
|
||||||
|
return "vault-0", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try other common Vault pod names
|
||||||
|
commonNames := []string{"vault-0", "vault-1", "vault-2"}
|
||||||
|
for _, name := range commonNames {
|
||||||
|
if err := runCommandQuiet("kubectl", "get", "pod", name, "-n", "vault"); err == nil {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find any vault pod using labels
|
||||||
|
cmd := exec.Command("kubectl", "get", "pods", "-n", "vault", "-l", "app.kubernetes.io/name=vault", "-o", "jsonpath={.items[0].metadata.name}")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err == nil && len(output) > 0 {
|
||||||
|
return string(output), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last resort: try to find any running pod in vault namespace
|
||||||
|
cmd = exec.Command("kubectl", "get", "pods", "-n", "vault", "--field-selector=status.phase=Running", "-o", "jsonpath={.items[0].metadata.name}")
|
||||||
|
output, err = cmd.Output()
|
||||||
|
if err == nil && len(output) > 0 {
|
||||||
|
return string(output), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("could not find any running Vault pods in the 'vault' namespace. Please ensure Vault is properly deployed and running")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getVaultRootToken retrieves the Vault root token from Kubernetes secrets
|
||||||
|
func getVaultRootToken() (string, error) {
|
||||||
|
fmt.Println("[INFO] Looking for Vault root token in vault-unseal-keys secret...")
|
||||||
|
|
||||||
|
// Primary location: secret/vault-unseal-keys key vault-root in vault namespace
|
||||||
|
cmd := exec.Command("kubectl", "get", "secret", "vault-unseal-keys", "-n", "vault", "-o", "jsonpath={.data.vault-root}")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get vault-unseal-keys secret from vault namespace: %v. Please ensure Vault is properly initialized", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(output) == 0 {
|
||||||
|
return "", fmt.Errorf("vault-root key not found in vault-unseal-keys secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The token is base64 encoded in the Kubernetes secret, decode it
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(string(output))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to decode base64 root token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(decoded) == 0 {
|
||||||
|
return "", fmt.Errorf("decoded root token is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
token := strings.TrimSpace(string(decoded))
|
||||||
|
fmt.Println("[SUCCESS] Found Vault root token")
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue