more updates
Publish Action Image / build (push) Successful in 1m8s

This commit is contained in:
2026-06-29 15:14:55 +10:00
parent 01223c176f
commit 4ee1b9e13c
15 changed files with 1155 additions and 832 deletions
+71 -89
View File
@@ -4,88 +4,11 @@ import (
"fmt"
"os"
"kforge/internal/config"
dnsProvider "kforge/internal/dns"
"kforge/internal/generator"
"github.com/spf13/cobra"
)
// ------------------------------------------------------------
// kforge dns ensure
// ------------------------------------------------------------
var dnsCmd = &cobra.Command{
Use: "dns",
Short: "Manage DNS records for kforge environments",
}
var dnsEnsureEnvs []string
var dnsEnsureCmd = &cobra.Command{
Use: "ensure",
Short: "Create or update DNS A records for ingress hosts",
Long: `For each ingress host with dns_record: true, creates or updates
an A record pointing to KFORGE_NODE_IP.
Idempotent — safe to run on every deploy. Skips hosts where the
record already points to the correct IP.
Examples:
kforge dns ensure --env staging
kforge dns ensure --env staging --env production`,
RunE: runDNSEnsure,
}
func init() {
dnsEnsureCmd.Flags().StringArrayVarP(&dnsEnsureEnvs, "env", "e", nil,
"Environment(s) to ensure DNS records for (default: all)")
dnsCmd.AddCommand(dnsEnsureCmd)
rootCmd.AddCommand(dnsCmd)
}
func runDNSEnsure(cmd *cobra.Command, args []string) error {
cfg, err := loadConfig()
if err != nil {
return err
}
if cfg.DNS.SkipDNS {
fmt.Println("dns.skip_dns is true — skipping DNS management")
return nil
}
nodeIP := cfg.DNS.NodeIP
if nodeIP == "" {
nodeIP = os.Getenv("KFORGE_NODE_IP")
}
if nodeIP == "" {
return fmt.Errorf("node IP not set: add dns.node_ip to kforge.yml or set KFORGE_NODE_IP")
}
provider, err := dnsProvider.NewProvider(cfg.DNS)
if err != nil {
return fmt.Errorf("initialising DNS provider: %w", err)
}
envKeys := dnsEnsureEnvs
if len(envKeys) == 0 {
envKeys = config.EnvironmentKeys(cfg)
}
for _, envKey := range envKeys {
fmt.Printf("\nEnsuring DNS records for: %s\n", envKey)
env, err := config.ResolveEnvironment(cfg, envKey)
if err != nil {
return err
}
if err := dnsProvider.EnsureRecordsForEnvironment(provider, &env, nodeIP); err != nil {
return fmt.Errorf("env %q: %w", envKey, err)
}
}
return nil
}
// ------------------------------------------------------------
// kforge gitea-actions
// ------------------------------------------------------------
@@ -98,16 +21,17 @@ var (
var giteaActionsCmd = &cobra.Command{
Use: "gitea-actions",
Short: "Generate a Gitea Actions workflow for this app",
Long: `Generates a complete .gitea/workflows/deploy.yml that:
- Builds and pushes the Docker image on every push to main
Short: "Generate a Gitea Actions deploy workflow for this app",
Long: `Generates .gitea/workflows/deploy.yml that:
- Builds and pushes the Docker image on push to main
- Runs kforge validate
- Applies cluster secrets (idempotent)
- Ensures DNS records
- Generates manifests and applies them with kubectl
- Rolls out the deployment
The generated workflow replaces your hand-written deploy.yml.
DNS is handled automatically by external-dns reading the Ingress
annotations that kforge writes — no separate DNS step needed.
Re-run whenever you add environments or change deploy options.
Examples:
@@ -142,29 +66,87 @@ func runGiteaActions(cmd *cobra.Command, args []string) error {
return fmt.Errorf("generating workflow: %w", err)
}
if giteaActionsOutput == "-" {
fmt.Print(workflow)
return writeWorkflow(giteaActionsOutput, workflow)
}
// ------------------------------------------------------------
// kforge gitea-preview
// ------------------------------------------------------------
var giteaPreviewOutput string
var giteaPreviewCmd = &cobra.Command{
Use: "gitea-preview",
Short: "Generate a Gitea Actions workflow for PR preview environments",
Long: `Generates .gitea/workflows/preview.yml that:
- On PR open/sync: builds a PR-tagged image, applies secrets,
generates manifests (including a Namespace), and deploys.
- On PR close: deletes the preview namespace and all resources.
Requires preview.enabled: true in kforge.yml.
Example kforge.yml preview block:
preview:
enabled: true
base_environment: staging
namespace_prefix: preview-pr
hostname_template: "pr-${PR_NUMBER}.${name}.example.com"
Examples:
kforge gitea-preview
kforge gitea-preview --output .gitea/workflows/preview.yml`,
RunE: runGiteaPreview,
}
func init() {
giteaPreviewCmd.Flags().StringVarP(&giteaPreviewOutput, "output", "o",
".gitea/workflows/preview.yml",
"Output path for the generated preview workflow file")
rootCmd.AddCommand(giteaPreviewCmd)
}
func runGiteaPreview(cmd *cobra.Command, args []string) error {
cfg, err := loadConfig()
if err != nil {
return err
}
workflow, err := generator.GeneratePreviewActions(cfg)
if err != nil {
return fmt.Errorf("generating preview workflow: %w", err)
}
return writeWorkflow(giteaPreviewOutput, workflow)
}
// ------------------------------------------------------------
// Shared helpers
// ------------------------------------------------------------
func writeWorkflow(path, content string) error {
if path == "-" {
fmt.Print(content)
return nil
}
// Ensure parent directory exists.
dir := giteaActionsOutput
dir := path
for i := len(dir) - 1; i >= 0; i-- {
if dir[i] == '/' {
if dir[i] == '/' || dir[i] == '\\' {
dir = dir[:i]
break
}
}
if dir != giteaActionsOutput {
if dir != path {
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("creating output directory: %w", err)
}
}
if err := os.WriteFile(giteaActionsOutput, []byte(workflow), 0o644); err != nil {
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
return fmt.Errorf("writing workflow: %w", err)
}
fmt.Printf("✓ Gitea Actions workflow written to %s\n", giteaActionsOutput)
fmt.Printf("✓ Workflow written to %s\n", path)
return nil
}