Merge pull request 'fix: reconcile delivery after reset' (#23) from fix/delivery-e2e-recovery into main
Reviewed-on: #23
This commit is contained in:
commit
2478f08e5c
|
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
var bootstrapConfigPath string
|
var bootstrapConfigPath string
|
||||||
var bootstrapOutputPath string
|
var bootstrapOutputPath string
|
||||||
|
var bootstrapWorkspaceDir string
|
||||||
var bootstrapMode string
|
var bootstrapMode string
|
||||||
var bootstrapYes bool
|
var bootstrapYes bool
|
||||||
var bootstrapPromptDemocraticCSI bool
|
var bootstrapPromptDemocraticCSI bool
|
||||||
|
|
@ -25,6 +26,7 @@ var bootstrapRotateWebhookAuthorization bool
|
||||||
var bootstrapPublishAppFrom string
|
var bootstrapPublishAppFrom string
|
||||||
var bootstrapMergeBootstrapPR bool
|
var bootstrapMergeBootstrapPR bool
|
||||||
var bootstrapManageNetworkBridges bool
|
var bootstrapManageNetworkBridges bool
|
||||||
|
var bootstrapEnableDelivery bool
|
||||||
|
|
||||||
var upsertOperationalSecret = bootstrap.UpsertOperationalSecret
|
var upsertOperationalSecret = bootstrap.UpsertOperationalSecret
|
||||||
var loadPublishAppConfig = config.Load
|
var loadPublishAppConfig = config.Load
|
||||||
|
|
@ -40,6 +42,7 @@ func init() {
|
||||||
rootCmd.AddCommand(bootstrapCmd)
|
rootCmd.AddCommand(bootstrapCmd)
|
||||||
bootstrapCmd.Flags().StringVar(&bootstrapConfigPath, "config", "", "Path to bootstrap config YAML")
|
bootstrapCmd.Flags().StringVar(&bootstrapConfigPath, "config", "", "Path to bootstrap config YAML")
|
||||||
bootstrapCmd.Flags().StringVar(&bootstrapOutputPath, "out", "maidn-bootstrap.yaml", "Path to save generated config")
|
bootstrapCmd.Flags().StringVar(&bootstrapOutputPath, "out", "maidn-bootstrap.yaml", "Path to save generated config")
|
||||||
|
bootstrapCmd.Flags().StringVar(&bootstrapWorkspaceDir, "workspace-dir", "", "Override workspace directory for this bootstrap run")
|
||||||
bootstrapCmd.Flags().StringVar(&bootstrapMode, "mode", string(bootstrap.Reconcile), "Lifecycle mode: reconcile or rebuild")
|
bootstrapCmd.Flags().StringVar(&bootstrapMode, "mode", string(bootstrap.Reconcile), "Lifecycle mode: reconcile or rebuild")
|
||||||
bootstrapCmd.Flags().BoolVar(&bootstrapYes, "yes", false, "Confirm destructive rebuild")
|
bootstrapCmd.Flags().BoolVar(&bootstrapYes, "yes", false, "Confirm destructive rebuild")
|
||||||
bootstrapCmd.Flags().BoolVar(&bootstrapPromptDemocraticCSI, "prompt-democratic-csi", false, "Prompt for and save Democratic CSI settings in --config")
|
bootstrapCmd.Flags().BoolVar(&bootstrapPromptDemocraticCSI, "prompt-democratic-csi", false, "Prompt for and save Democratic CSI settings in --config")
|
||||||
|
|
@ -52,6 +55,7 @@ func init() {
|
||||||
bootstrapCmd.Flags().StringVar(&bootstrapPublishAppFrom, "publish-app-from", "", "Push this app checkout's current branch and create a Forgejo delivery PR")
|
bootstrapCmd.Flags().StringVar(&bootstrapPublishAppFrom, "publish-app-from", "", "Push this app checkout's current branch and create a Forgejo delivery PR")
|
||||||
bootstrapCmd.Flags().BoolVar(&bootstrapMergeBootstrapPR, "merge-bootstrap-pr", false, "Merge the generated Flux repository migration PR before bootstrapping")
|
bootstrapCmd.Flags().BoolVar(&bootstrapMergeBootstrapPR, "merge-bootstrap-pr", false, "Merge the generated Flux repository migration PR before bootstrapping")
|
||||||
bootstrapCmd.Flags().BoolVar(&bootstrapManageNetworkBridges, "manage-network-bridges", false, "Persist Terraform management for existing Talos network bridges")
|
bootstrapCmd.Flags().BoolVar(&bootstrapManageNetworkBridges, "manage-network-bridges", false, "Persist Terraform management for existing Talos network bridges")
|
||||||
|
bootstrapCmd.Flags().BoolVar(&bootstrapEnableDelivery, "enable-delivery", false, "Resolve delivery defaults and reconcile the configured app delivery source")
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBootstrap(cmd *cobra.Command, args []string) error {
|
func runBootstrap(cmd *cobra.Command, args []string) error {
|
||||||
|
|
@ -237,6 +241,9 @@ func runBootstrap(cmd *cobra.Command, args []string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if bootstrapWorkspaceDir != "" {
|
||||||
|
cfg.WorkspaceDir = bootstrapWorkspaceDir
|
||||||
|
}
|
||||||
if bootstrapRegisterWebhook {
|
if bootstrapRegisterWebhook {
|
||||||
cfg, err = config.ResolveDelivery(cfg)
|
cfg, err = config.ResolveDelivery(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -247,7 +254,7 @@ func runBootstrap(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runner := bootstrap.Runner{Config: cfg, Mode: bootstrap.Mode(bootstrapMode), ConfirmRebuild: bootstrapYes, RegisterWebhook: bootstrapRegisterWebhook}
|
runner := bootstrap.Runner{Config: cfg, Mode: bootstrap.Mode(bootstrapMode), ConfirmRebuild: bootstrapYes, RegisterWebhook: bootstrapRegisterWebhook, EnableDelivery: bootstrapEnableDelivery}
|
||||||
return runner.Run()
|
return runner.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ type Runner struct {
|
||||||
Mode Mode
|
Mode Mode
|
||||||
ConfirmRebuild bool
|
ConfirmRebuild bool
|
||||||
RegisterWebhook bool
|
RegisterWebhook bool
|
||||||
|
EnableDelivery bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type operationalSecrets struct {
|
type operationalSecrets struct {
|
||||||
|
|
@ -143,7 +144,7 @@ func (r Runner) Run() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.Config = resolved
|
r.Config = resolved
|
||||||
if r.RegisterWebhook {
|
if r.RegisterWebhook || r.EnableDelivery {
|
||||||
resolvedDelivery, err := config.ResolveDelivery(r.Config)
|
resolvedDelivery, err := config.ResolveDelivery(r.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -441,7 +442,7 @@ func copyAndRenderDeliveryBases(templateDir, repoDir string, cfg config.Config)
|
||||||
if err := writePreviewDeliveryConfig(dir, cfg); err != nil {
|
if err := writePreviewDeliveryConfig(dir, cfg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return writeAppDeliverySource(dir, cfg)
|
return removeDuplicateAppDeliverySource(dir, cfg.Delivery.AppName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writePreviewDeliveryConfig(dir string, cfg config.Config) error {
|
func writePreviewDeliveryConfig(dir string, cfg config.Config) error {
|
||||||
|
|
@ -476,50 +477,23 @@ func writePreviewDeliveryConfig(dir string, cfg config.Config) error {
|
||||||
if !strings.Contains("\n"+string(data), "\nresources:") {
|
if !strings.Contains("\n"+string(data), "\nresources:") {
|
||||||
return errors.New("Tekton Kustomization must define resources before adding preview delivery configuration")
|
return errors.New("Tekton Kustomization must define resources before adding preview delivery configuration")
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(filepath.Join(dir, "maidn-preview-delivery-config.yaml"), content, 0644); err != nil {
|
if err := os.WriteFile(filepath.Join(dir, "preview-delivery-config.yaml"), content, 0644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if strings.Contains(string(data), "maidn-preview-delivery-config.yaml") {
|
legacyPath := filepath.Join(dir, "maidn-preview-delivery-config.yaml")
|
||||||
return nil
|
if err := os.Remove(legacyPath); err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return os.WriteFile(path, append(data, []byte(" - maidn-preview-delivery-config.yaml\n")...), 0644)
|
updated := strings.ReplaceAll(string(data), " - maidn-preview-delivery-config.yaml\n", "")
|
||||||
|
if !strings.Contains(updated, "preview-delivery-config.yaml") {
|
||||||
|
updated += " - preview-delivery-config.yaml\n"
|
||||||
|
}
|
||||||
|
return os.WriteFile(path, []byte(updated), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeAppDeliverySource(dir string, cfg config.Config) error {
|
func removeDuplicateAppDeliverySource(dir, appName string) error {
|
||||||
if err := config.ValidateDelivery(cfg); err != nil {
|
filename := appName + "-source.yaml"
|
||||||
return err
|
if err := os.Remove(filepath.Join(dir, filename)); err != nil && !os.IsNotExist(err) {
|
||||||
}
|
|
||||||
name := cfg.Delivery.AppName
|
|
||||||
filename := name + "-source.yaml"
|
|
||||||
content := fmt.Sprintf(`apiVersion: source.toolkit.fluxcd.io/v1
|
|
||||||
kind: GitRepository
|
|
||||||
metadata:
|
|
||||||
name: %s
|
|
||||||
namespace: flux-system
|
|
||||||
spec:
|
|
||||||
interval: 5m
|
|
||||||
url: %s
|
|
||||||
ref:
|
|
||||||
branch: %s
|
|
||||||
secretRef:
|
|
||||||
name: forgejo-flux-credentials
|
|
||||||
---
|
|
||||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
|
||||||
kind: Kustomization
|
|
||||||
metadata:
|
|
||||||
name: %s-delivery
|
|
||||||
namespace: flux-system
|
|
||||||
spec:
|
|
||||||
dependsOn:
|
|
||||||
- name: tekton-catalog
|
|
||||||
interval: 5m
|
|
||||||
path: ./.tekton
|
|
||||||
prune: true
|
|
||||||
sourceRef:
|
|
||||||
kind: GitRepository
|
|
||||||
name: %s
|
|
||||||
`, name, cfg.Delivery.AppRepoURL, cfg.Delivery.AppRepoRef, name, name)
|
|
||||||
if err := os.WriteFile(filepath.Join(dir, filename), []byte(content), 0644); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
path := filepath.Join(dir, "kustomization.yaml")
|
path := filepath.Join(dir, "kustomization.yaml")
|
||||||
|
|
@ -527,13 +501,11 @@ spec:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !strings.Contains("\n"+string(data), "\nresources:") {
|
updated := strings.ReplaceAll(string(data), " - "+filename+"\n", "")
|
||||||
return errors.New("Tekton Kustomization must define resources before adding app delivery source")
|
if updated == string(data) {
|
||||||
}
|
|
||||||
if strings.Contains(string(data), filename) {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return os.WriteFile(path, append(data, []byte(" - "+filename+"\n")...), 0644)
|
return os.WriteFile(path, []byte(updated), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func canonicalForgejoOrigin(value string) (string, error) {
|
func canonicalForgejoOrigin(value string) (string, error) {
|
||||||
|
|
|
||||||
|
|
@ -69,25 +69,22 @@ func TestRenderDeliveryConfig(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWriteAppDeliverySource(t *testing.T) {
|
func TestRemoveDuplicateAppDeliverySource(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
if err := os.WriteFile(filepath.Join(dir, "kustomization.yaml"), []byte("resources:\n"), 0644); err != nil {
|
if err := os.WriteFile(filepath.Join(dir, "kustomization.yaml"), []byte("resources:\n - easycsr-frontend-source.yaml\n - delivery-source.yaml\n"), 0644); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
cfg := config.Config{
|
if err := os.WriteFile(filepath.Join(dir, "easycsr-frontend-source.yaml"), []byte("stale: source\n"), 0644); err != nil {
|
||||||
Git: config.GitConfig{BaseURL: "https://git.example.test", Owner: "apps"},
|
|
||||||
Flux: config.FluxConfig{Branch: "main", ManifestsRepo: "manifests"},
|
|
||||||
Delivery: config.DeliveryConfig{AppName: "web-ui", AppRepoURL: "https://git.example.test/apps/web-ui.git", AppRepoRef: "main", ProductionBranch: "production", ImageRepository: "registry.example.test/apps/web-ui", BuildOutputDirectory: "dist", BuildConfiguration: "production", WebhookHostname: "tekton.example.test", WebhookPath: "/"},
|
|
||||||
}
|
|
||||||
if err := writeAppDeliverySource(dir, cfg); err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
content, err := os.ReadFile(filepath.Join(dir, "web-ui-source.yaml"))
|
if err := removeDuplicateAppDeliverySource(dir, "easycsr-frontend"); err != nil {
|
||||||
if err != nil || !strings.Contains(string(content), "name: web-ui-delivery") || strings.Contains(string(content), "token") {
|
t.Fatal(err)
|
||||||
t.Fatalf("app delivery source = %q, %v", content, err)
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(dir, "easycsr-frontend-source.yaml")); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("duplicate app source remains: %v", err)
|
||||||
}
|
}
|
||||||
kustomization, err := os.ReadFile(filepath.Join(dir, "kustomization.yaml"))
|
kustomization, err := os.ReadFile(filepath.Join(dir, "kustomization.yaml"))
|
||||||
if err != nil || !strings.Contains(string(kustomization), "web-ui-source.yaml") {
|
if err != nil || strings.Contains(string(kustomization), "easycsr-frontend-source.yaml") || !strings.Contains(string(kustomization), "delivery-source.yaml") {
|
||||||
t.Fatalf("Tekton Kustomization = %q, %v", kustomization, err)
|
t.Fatalf("Tekton Kustomization = %q, %v", kustomization, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -132,10 +129,17 @@ func TestWritePreviewDeliveryConfigIsTrustedAndNonSecret(t *testing.T) {
|
||||||
if err := writePreviewDeliveryConfig(dir, cfg); err != nil {
|
if err := writePreviewDeliveryConfig(dir, cfg); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
content, err := os.ReadFile(filepath.Join(dir, "maidn-preview-delivery-config.yaml"))
|
content, err := os.ReadFile(filepath.Join(dir, "preview-delivery-config.yaml"))
|
||||||
if err != nil || !strings.Contains(string(content), "forgejo-origin: https://git.example.test") || strings.Contains(string(content), "forgejo-base-url") || !strings.Contains(string(content), "manifests-url: https://git.example.test/platform/manifests.git") || strings.Contains(string(content), "secret") || strings.Contains(string(content), "dynamic-app") {
|
if err != nil || !strings.Contains(string(content), "forgejo-origin: https://git.example.test") || strings.Contains(string(content), "forgejo-base-url") || !strings.Contains(string(content), "manifests-url: https://git.example.test/platform/manifests.git") || strings.Contains(string(content), "secret") || strings.Contains(string(content), "dynamic-app") {
|
||||||
t.Fatalf("preview delivery config is not trusted and non-secret: %q, %v", content, err)
|
t.Fatalf("preview delivery config is not trusted and non-secret: %q, %v", content, err)
|
||||||
}
|
}
|
||||||
|
if _, err := os.Stat(filepath.Join(dir, "maidn-preview-delivery-config.yaml")); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("obsolete preview config remains: %v", err)
|
||||||
|
}
|
||||||
|
kustomization, err := os.ReadFile(filepath.Join(dir, "kustomization.yaml"))
|
||||||
|
if err != nil || strings.Contains(string(kustomization), "maidn-preview-delivery-config.yaml") || !strings.Contains(string(kustomization), "preview-delivery-config.yaml") {
|
||||||
|
t.Fatalf("preview config resource was not replaced: %q, %v", kustomization, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWritePreviewDeliveryConfigRejectsAmbiguousKustomization(t *testing.T) {
|
func TestWritePreviewDeliveryConfigRejectsAmbiguousKustomization(t *testing.T) {
|
||||||
|
|
@ -744,6 +748,28 @@ func TestRunnerAutoBootstrapFluxIgnoresPartialLegacyDelivery(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunnerEnableDeliveryAppliesDefaults(t *testing.T) {
|
||||||
|
originalPreflight := preflight
|
||||||
|
t.Cleanup(func() { preflight = originalPreflight })
|
||||||
|
preflight = func(cfg config.Config) error {
|
||||||
|
if !cfg.Delivery.Configured() {
|
||||||
|
t.Fatal("delivery defaults were not applied")
|
||||||
|
}
|
||||||
|
return errors.New("reached preflight")
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := runnerTestConfig(t.TempDir(), "")
|
||||||
|
cfg.Delivery.AppRepoRef = ""
|
||||||
|
cfg.Delivery.BuildOutputDirectory = ""
|
||||||
|
cfg.Delivery.BuildConfiguration = ""
|
||||||
|
cfg.Delivery.WebhookHostname = ""
|
||||||
|
cfg.Delivery.WebhookPath = ""
|
||||||
|
err := (Runner{Config: cfg, EnableDelivery: true}).Run()
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "reached preflight") {
|
||||||
|
t.Fatalf("enable delivery did not resolve defaults before preflight: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRunnerRegisterWebhookSkipsTemplateRevisions(t *testing.T) {
|
func TestRunnerRegisterWebhookSkipsTemplateRevisions(t *testing.T) {
|
||||||
originalPreflight := preflight
|
originalPreflight := preflight
|
||||||
originalGit := runGit
|
originalGit := runGit
|
||||||
|
|
|
||||||
|
|
@ -325,7 +325,12 @@ func refreshExternalSecrets(kubeconfig string) error {
|
||||||
if err != nil || strings.TrimSpace(string(available)) != "True" {
|
if err != nil || strings.TrimSpace(string(available)) != "True" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
_, err = kubectlOutput(kubeconfig, externalSecretRefreshArgs(time.Now().UnixNano())...)
|
timestamp := time.Now().UnixNano()
|
||||||
|
_, err = kubectlOutput(kubeconfig, "annotate", "clustersecretstore", "openbao", fmt.Sprintf("force-sync=%d", timestamp), "--overwrite")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("refresh OpenBao secret store after seed: %w", err)
|
||||||
|
}
|
||||||
|
_, err = kubectlOutput(kubeconfig, externalSecretRefreshArgs(timestamp)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("refresh ExternalSecrets after OpenBao seed: %w", err)
|
return fmt.Errorf("refresh ExternalSecrets after OpenBao seed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ func TestRefreshExternalSecretsIsReadyGatedAndScoped(t *testing.T) {
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if err := refreshExternalSecrets("kubeconfig"); err != nil || len(calls) != 2 || !strings.Contains(calls[0], "get deployment/external-secrets") || !strings.Contains(calls[1], "annotate externalsecret forgejo-webhook") || strings.Contains(calls[1], "--all") {
|
if err := refreshExternalSecrets("kubeconfig"); err != nil || len(calls) != 3 || !strings.Contains(calls[0], "get deployment/external-secrets") || !strings.Contains(calls[1], "annotate clustersecretstore openbao") || !strings.Contains(calls[2], "annotate externalsecret forgejo-webhook") || strings.Contains(calls[1], "--all") || strings.Contains(calls[2], "--all") {
|
||||||
t.Fatalf("ExternalSecret refresh was not readiness-gated and scoped: %q, %v", calls, err)
|
t.Fatalf("ExternalSecret refresh was not readiness-gated and scoped: %q, %v", calls, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue