feat: review app onboarding changes #57

Merged
eding merged 2 commits from feat/reviewed-app-onboarding into main 2026-09-16 09:06:20 +02:00
7 changed files with 137 additions and 41 deletions
Showing only changes of commit 799a51b485 - Show all commits

View file

@ -17,7 +17,7 @@ dlv version
- `cicd-tool bootstrap` runs a shorter Forgejo-first wizard, asks for a Forgejo PAT, asks where local repos should be cloned, discovers Proxmox nodes/storage/networks, retries without losing entered answers when discovery fails, shows the latest Talos version, derives the standardized Talos factory URL, schematic, and required extensions automatically from the chosen version, writes `terraform.tfvars`, stages Talos images on Proxmox, and can execute Terraform, Talos bootstrap, and Flux bootstrap - `cicd-tool bootstrap` runs a shorter Forgejo-first wizard, asks for a Forgejo PAT, asks where local repos should be cloned, discovers Proxmox nodes/storage/networks, retries without losing entered answers when discovery fails, shows the latest Talos version, derives the standardized Talos factory URL, schematic, and required extensions automatically from the chosen version, writes `terraform.tfvars`, stages Talos images on Proxmox, and can execute Terraform, Talos bootstrap, and Flux bootstrap
- `cicd-tool bootstrap --config maidn-bootstrap.yaml` skips the wizard and uses the saved config - `cicd-tool bootstrap --config maidn-bootstrap.yaml` skips the wizard and uses the saved config
- `maidn bootstrap init --config <private-config> --organization <new-org> --create-organization --enable-delivery` locks an isolated workspace, initializes the Forgejo repositories, then runs the non-destructive bootstrap reconcile lifecycle; use `--mode=rebuild --yes` for an authorized rebuild. Delivery scaffolding requires `--enable-delivery`. - `maidn bootstrap init --config <private-config> --organization <new-org> --create-organization --enable-delivery` locks an isolated workspace, initializes the Forgejo repositories, then runs the non-destructive bootstrap reconcile lifecycle; use `--mode=rebuild --yes` for an authorized rebuild. Delivery scaffolding requires `--enable-delivery`.
- `maidn app onboard --config <private-config> --from <app-checkout>` validates a clean configured checkout and adds its `.tekton` delivery contract. - `maidn app onboard --config <private-config> --from <app-checkout>` opens reviewed source-delivery and cluster-registration PRs; see `docs/operations.md`.
- `cicd-tool e2e` runs bounded, read-only Flux, ExternalSecret, PipelineRun, preview, and promotion-PR checks with JSON output. See `docs/e2e.md`. - `cicd-tool e2e` runs bounded, read-only Flux, ExternalSecret, PipelineRun, preview, and promotion-PR checks with JSON output. See `docs/e2e.md`.
See `docs/operations.md` for the authorized operating and verification runbook. See `docs/operations.md` for the authorized operating and verification runbook.

View file

@ -9,7 +9,7 @@ import (
) )
var freshConfigPath, freshOrganization, onboardConfigPath, onboardFrom string var freshConfigPath, freshOrganization, onboardConfigPath, onboardFrom string
var onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy string var onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy, onboardBuildOutputDirectory, onboardBuildConfiguration string
var freshCreateOrganization, freshEnableDelivery, freshYes bool var freshCreateOrganization, freshEnableDelivery, freshYes bool
var freshMode string var freshMode string
@ -55,6 +55,8 @@ func init() {
appOnboardCmd.Flags().StringVar(&onboardAppRepoURL, "app-repo-url", "", "Application repository URL override") appOnboardCmd.Flags().StringVar(&onboardAppRepoURL, "app-repo-url", "", "Application repository URL override")
appOnboardCmd.Flags().StringVar(&onboardImageRepository, "image-repository", "", "OCI image repository override") appOnboardCmd.Flags().StringVar(&onboardImageRepository, "image-repository", "", "OCI image repository override")
appOnboardCmd.Flags().StringVar(&onboardBuildStrategy, "build-strategy", "", "Build strategy override: static or runtime") appOnboardCmd.Flags().StringVar(&onboardBuildStrategy, "build-strategy", "", "Build strategy override: static or runtime")
appOnboardCmd.Flags().StringVar(&onboardBuildOutputDirectory, "build-output-directory", "", "Static build output directory override")
appOnboardCmd.Flags().StringVar(&onboardBuildConfiguration, "build-configuration", "", "Static build configuration override")
_ = appOnboardCmd.MarkFlagRequired("config") _ = appOnboardCmd.MarkFlagRequired("config")
_ = appOnboardCmd.MarkFlagRequired("from") _ = appOnboardCmd.MarkFlagRequired("from")
} }
@ -91,6 +93,12 @@ func runAppOnboard(_ *cobra.Command, _ []string) error {
if onboardBuildStrategy != "" { if onboardBuildStrategy != "" {
cfg.Delivery.BuildStrategy = onboardBuildStrategy cfg.Delivery.BuildStrategy = onboardBuildStrategy
} }
if onboardBuildOutputDirectory != "" {
cfg.Delivery.BuildOutputDirectory = onboardBuildOutputDirectory
}
if onboardBuildConfiguration != "" {
cfg.Delivery.BuildConfiguration = onboardBuildConfiguration
}
cfg, err = resolveAppOnboarding(cfg) cfg, err = resolveAppOnboarding(cfg)
if err != nil { if err != nil {
return err return err

View file

@ -65,11 +65,11 @@ func TestBootstrapInitAppliesFluxDefaultsBeforeFreshValidation(t *testing.T) {
func TestAppOnboardValidatesConfigBeforeExternalWork(t *testing.T) { func TestAppOnboardValidatesConfigBeforeExternalWork(t *testing.T) {
originalConfig, originalResolve, originalOnboard := loadAppOnboardConfig, resolveAppOnboarding, onboardApp originalConfig, originalResolve, originalOnboard := loadAppOnboardConfig, resolveAppOnboarding, onboardApp
originalConfigPath, originalFrom := onboardConfigPath, onboardFrom originalConfigPath, originalFrom := onboardConfigPath, onboardFrom
originalName, originalRepo, originalImage, originalBuildStrategy := onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy originalName, originalRepo, originalImage, originalBuildStrategy, originalBuildOutput, originalBuildConfiguration := onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy, onboardBuildOutputDirectory, onboardBuildConfiguration
t.Cleanup(func() { t.Cleanup(func() {
loadAppOnboardConfig, resolveAppOnboarding, onboardApp = originalConfig, originalResolve, originalOnboard loadAppOnboardConfig, resolveAppOnboarding, onboardApp = originalConfig, originalResolve, originalOnboard
onboardConfigPath, onboardFrom = originalConfigPath, originalFrom onboardConfigPath, onboardFrom = originalConfigPath, originalFrom
onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy = originalName, originalRepo, originalImage, originalBuildStrategy onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy, onboardBuildOutputDirectory, onboardBuildConfiguration = originalName, originalRepo, originalImage, originalBuildStrategy, originalBuildOutput, originalBuildConfiguration
}) })
loadAppOnboardConfig = func(string) (config.Config, error) { return config.Config{}, nil } loadAppOnboardConfig = func(string) (config.Config, error) { return config.Config{}, nil }
resolveAppOnboarding = func(config.Config) (config.Config, error) { return config.Config{}, errors.New("incomplete delivery") } resolveAppOnboarding = func(config.Config) (config.Config, error) { return config.Config{}, errors.New("incomplete delivery") }
@ -78,7 +78,7 @@ func TestAppOnboardValidatesConfigBeforeExternalWork(t *testing.T) {
return nil return nil
} }
onboardConfigPath, onboardFrom = "private.yaml", "app-checkout" onboardConfigPath, onboardFrom = "private.yaml", "app-checkout"
onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy = "", "", "", "" onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy, onboardBuildOutputDirectory, onboardBuildConfiguration = "", "", "", "", "", ""
if err := runAppOnboard(nil, nil); err == nil { if err := runAppOnboard(nil, nil); err == nil {
t.Fatal("onboarding accepted invalid configuration") t.Fatal("onboarding accepted invalid configuration")
} }
@ -87,25 +87,25 @@ func TestAppOnboardValidatesConfigBeforeExternalWork(t *testing.T) {
func TestAppOnboardPassesOnlyValidatedConfigAndCheckout(t *testing.T) { func TestAppOnboardPassesOnlyValidatedConfigAndCheckout(t *testing.T) {
originalConfig, originalResolve, originalOnboard := loadAppOnboardConfig, resolveAppOnboarding, onboardApp originalConfig, originalResolve, originalOnboard := loadAppOnboardConfig, resolveAppOnboarding, onboardApp
originalConfigPath, originalFrom := onboardConfigPath, onboardFrom originalConfigPath, originalFrom := onboardConfigPath, onboardFrom
originalName, originalRepo, originalImage, originalBuildStrategy := onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy originalName, originalRepo, originalImage, originalBuildStrategy, originalBuildOutput, originalBuildConfiguration := onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy, onboardBuildOutputDirectory, onboardBuildConfiguration
t.Cleanup(func() { t.Cleanup(func() {
loadAppOnboardConfig, resolveAppOnboarding, onboardApp = originalConfig, originalResolve, originalOnboard loadAppOnboardConfig, resolveAppOnboarding, onboardApp = originalConfig, originalResolve, originalOnboard
onboardConfigPath, onboardFrom = originalConfigPath, originalFrom onboardConfigPath, onboardFrom = originalConfigPath, originalFrom
onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy = originalName, originalRepo, originalImage, originalBuildStrategy onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy, onboardBuildOutputDirectory, onboardBuildConfiguration = originalName, originalRepo, originalImage, originalBuildStrategy, originalBuildOutput, originalBuildConfiguration
}) })
cfg := config.Config{Delivery: config.DeliveryConfig{AppRepoURL: "https://git.example.test/new-org/app.git", AppRepoRef: "main"}} cfg := config.Config{Delivery: config.DeliveryConfig{AppRepoURL: "https://git.example.test/new-org/app.git", AppRepoRef: "main"}}
loadAppOnboardConfig = func(string) (config.Config, error) { return cfg, nil } loadAppOnboardConfig = func(string) (config.Config, error) { return cfg, nil }
resolveAppOnboarding = func(got config.Config) (config.Config, error) { return got, nil } resolveAppOnboarding = func(got config.Config) (config.Config, error) { return got, nil }
calls := 0 calls := 0
onboardApp = func(got config.Config, checkout string) error { onboardApp = func(got config.Config, checkout string) error {
if checkout != "app-checkout" || got.Delivery.AppName != "fixture" || got.Delivery.AppRepoURL != "https://git.example.test/new-org/fixture.git" || got.Delivery.ImageRepository != "registry.example.test/new-org/fixture" || got.Delivery.BuildStrategy != "runtime" { if checkout != "app-checkout" || got.Delivery.AppName != "fixture" || got.Delivery.AppRepoURL != "https://git.example.test/new-org/fixture.git" || got.Delivery.ImageRepository != "registry.example.test/new-org/fixture" || got.Delivery.BuildStrategy != "runtime" || got.Delivery.BuildOutputDirectory != "dist/fixture" || got.Delivery.BuildConfiguration != "ci" {
t.Fatal("onboarding used the wrong checkout or config") t.Fatal("onboarding used the wrong checkout or config")
} }
calls++ calls++
return nil return nil
} }
onboardConfigPath, onboardFrom = "private.yaml", "app-checkout" onboardConfigPath, onboardFrom = "private.yaml", "app-checkout"
onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy = "fixture", "https://git.example.test/new-org/fixture.git", "registry.example.test/new-org/fixture", "runtime" onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy, onboardBuildOutputDirectory, onboardBuildConfiguration = "fixture", "https://git.example.test/new-org/fixture.git", "registry.example.test/new-org/fixture", "runtime", "dist/fixture", "ci"
if err := runAppOnboard(nil, nil); err != nil || calls != 1 { if err := runAppOnboard(nil, nil); err != nil || calls != 1 {
t.Fatalf("runAppOnboard() = %v, calls = %d", err, calls) t.Fatalf("runAppOnboard() = %v, calls = %d", err, calls)
} }

View file

@ -23,31 +23,73 @@ new template revisions intentionally, use a new empty `workspaceDir` (and a
fresh `cloneParent` when it is configured separately) and keep the prior fresh `cloneParent` when it is configured separately) and keep the prior
secret-bearing workspace intact for recovery. secret-bearing workspace intact for recovery.
## Platform Initialization And App Onboarding ## Standard Delivery Workflow
Initialize the shared delivery platform before onboarding any application. This Open and merge reviewed platform-source PRs before reconciling. When a merged
template source must replace an existing revision lock, run the refresh by
itself. It creates migration PRs; review and merge them, then rerun normal
reconciliation:
```powershell
go run . bootstrap --config <private-bootstrap-config> --mode=reconcile --refresh-template-revisions
go run . bootstrap --config <private-bootstrap-config> --mode=reconcile
```
Initialize the shared delivery platform before onboarding an application. This
creates the Gateway, Tekton, and Tekton Triggers platform resources using creates the Gateway, Tekton, and Tekton Triggers platform resources using
`tekton.<cluster-domain>` and the configured Forgejo owner; it does not render `tekton.<cluster-domain>` and the configured Forgejo owner:
an application Pipeline or register an application webhook:
```powershell ```powershell
bootstrap init --config <private-bootstrap-config> --organization <owner> --create-organization go run . bootstrap init --config <private-bootstrap-config> --organization <owner> --create-organization
``` ```
Import a clean checkout into the configured owner with a separate command. The Use a clean checkout on `delivery.appRepoRef`. The app repository URL must be
checkout may originate in another Forgejo organization, but `delivery.appRepoUrl` the canonical source owner, such as `Maidn/<app>.git`; `test-org-2` is only the
must target `<owner>/<app>.git` and its current branch must be execution owner. Set per-app static build values with command-line overrides,
`delivery.appRepoRef`: not by rewriting the private default:
```powershell ```powershell
app onboard --config <private-bootstrap-config> --from <clean-checkout> go run . app onboard --config <private-bootstrap-config> --from <clean-checkout> `
--app-name <app> --app-repo-url https://<forgejo>/Maidn/<app>.git `
--image-repository <registry>/<owner>/<app> --build-strategy static `
--build-output-directory <output-directory> --build-configuration <configuration>
``` ```
Onboarding copies the current branch to the target repository, replaces only For a runtime build, set `--build-strategy runtime`; static-only output options
the two Maidn-generated `.tekton` files, protects the production branch, and remain harmless. Onboarding opens, but never merges, a source delivery PR and a
auto-merges the app delivery and cluster registration PRs. The registration is cluster registration PR. It registers the Forgejo hook but does not emit a test
stored in `base/tekton/apps/<app>.yaml` in the configured cluster repository; delivery. Review and merge the source delivery PR first, then the cluster
the generic EventListener dispatches by the Forgejo repository name. registration PR. The registration is stored in
`base/tekton/apps/<app>.yaml`; Flux loads the source-owned `.tekton` path and
the generic EventListener dispatches by Forgejo repository name.
After Flux reports the app Kustomization Ready, use Forgejo's hook test endpoint
against a non-`main` ref and inspect the resulting PipelineRun. The command and
read-only checks are in [Webhook Smoke Test](#webhook-smoke-test).
### Shared Services And Add-ons
Environment databases are platform-owned shared services. Staging workloads use
the CNPG-generated `staging-postgres-app` Secret and production workloads use
`production-postgres-app`; applications must not declare their own CNPG Cluster
by default. Shared credentials are appropriate only for the shared environment
database. Use a dedicated service only when isolation, lifecycle, or storage
requirements demand it.
An application can carry reviewed dedicated resources in
`.maidn/kustomization.yaml`. Onboarding registers that path as the app's
`<app>-addons` Flux Kustomization without changing the generated `.tekton`
files. Runtime secret grants also generate the app's `.maidn/secret-access.yaml`
there. Add-ons must declare their namespace explicitly and contain references,
never credential values. Preview namespaces do not receive staging or production
runtime credentials; preview-safe configuration is the application chart's
responsibility.
Declare runtime secret access in the private configuration and run normal
bootstrap reconciliation to create its policy and role. Set values only with
`app secret set` using stdin, `--file`, or `--generate`; provision the scoped
E2E identity with `bootstrap --provision-app-secret-identities --e2e-app <app>`
only when a probe needs it. See [secret-grants.md](secret-grants.md).
`delivery.productionBranch` is the application production branch. A production `delivery.productionBranch` is the application production branch. A production
delivery PR targets the configured generic delivery PR targets the configured generic

View file

@ -22,15 +22,13 @@ type onboardingRepoManager interface {
EnsurePullRequest(string, string, string, string) error EnsurePullRequest(string, string, string, string) error
PublishRepositoryPullRequest(string, string, string, string, func(string) error) (bool, error) PublishRepositoryPullRequest(string, string, string, string, func(string) error) (bool, error)
EnsureWebhook(string, string, string) error EnsureWebhook(string, string, string) error
TriggerWebhookTest(string, string, string) error
} }
var newOnboardingRepoManager = func(baseURL, token, owner, username, manifestsRepo, fluxRepo, branch, migrationBranch string) onboardingRepoManager { var newOnboardingRepoManager = func(baseURL, token, owner, username, manifestsRepo, fluxRepo, branch, migrationBranch string) onboardingRepoManager {
return forgejo.NewRepoManager(baseURL, token, owner, username, manifestsRepo, fluxRepo, branch, migrationBranch) return forgejo.NewRepoManager(baseURL, token, owner, username, manifestsRepo, fluxRepo, branch, migrationBranch)
} }
// OnboardApp imports one clean checkout into its source Forgejo owner, // OnboardApp opens reviewed source-delivery and cluster-registration pull requests.
// publishes its delivery branch, registers it with Flux, then adds its webhook.
func OnboardApp(cfg config.Config, sourceDir string) error { func OnboardApp(cfg config.Config, sourceDir string) error {
resolved, err := config.ResolveAppOnboarding(cfg) resolved, err := config.ResolveAppOnboarding(cfg)
if err != nil { if err != nil {
@ -46,6 +44,10 @@ func OnboardApp(cfg config.Config, sourceDir string) error {
if sourceBranch != resolved.Delivery.AppRepoRef { if sourceBranch != resolved.Delivery.AppRepoRef {
return errors.New("--from branch must match delivery appRepoRef") return errors.New("--from branch must match delivery appRepoRef")
} }
hasAddons, err := hasMaidnAddons(sourceDir)
if err != nil {
return err
}
owner, repository, err := forgejo.RepositoryFromURL(resolved.Delivery.AppRepoURL) owner, repository, err := forgejo.RepositoryFromURL(resolved.Delivery.AppRepoURL)
if err != nil { if err != nil {
return err return err
@ -82,7 +84,7 @@ func OnboardApp(cfg config.Config, sourceDir string) error {
registrationBranch := "maidn/register-" + resolved.Delivery.AppName registrationBranch := "maidn/register-" + resolved.Delivery.AppName
clusterManager := newOnboardingRepoManager(resolved.Git.BaseURL, resolved.Git.Token, resolved.Git.Owner, resolved.Git.Username, "", "", resolved.Flux.Branch, "") clusterManager := newOnboardingRepoManager(resolved.Git.BaseURL, resolved.Git.Token, resolved.Git.Owner, resolved.Git.Username, "", "", resolved.Flux.Branch, "")
if _, err := clusterManager.PublishRepositoryPullRequest(resolved.Flux.RepoName, "feat: register "+resolved.Delivery.AppName+" delivery", registrationBranch, resolved.Flux.Branch, func(dir string) error { if _, err := clusterManager.PublishRepositoryPullRequest(resolved.Flux.RepoName, "feat: register "+resolved.Delivery.AppName+" delivery", registrationBranch, resolved.Flux.Branch, func(dir string) error {
return RegisterAppInCluster(dir, resolved) return registerAppInCluster(dir, resolved, hasAddons || hasRuntimeSecretGrant(resolved))
}); err != nil { }); err != nil {
return fmt.Errorf("register app in cluster repository: %w", err) return fmt.Errorf("register app in cluster repository: %w", err)
} }
@ -102,12 +104,17 @@ func OnboardApp(cfg config.Config, sourceDir string) error {
if err := sourceManager.EnsureWebhook(repository, webhookURL, authorization); err != nil { if err := sourceManager.EnsureWebhook(repository, webhookURL, authorization); err != nil {
return fmt.Errorf("register Forgejo webhook: %w", err) return fmt.Errorf("register Forgejo webhook: %w", err)
} }
if err := sourceManager.TriggerWebhookTest(repository, webhookURL, resolved.Delivery.AppRepoRef); err != nil {
return fmt.Errorf("trigger Forgejo webhook test: %w", err)
}
return nil return nil
} }
func hasMaidnAddons(dir string) (bool, error) {
_, err := readRegularFile(filepath.Join(dir, ".maidn", "kustomization.yaml"))
if errors.Is(err, os.ErrNotExist) {
return false, nil
}
return err == nil, err
}
// publishInitialAppBranches establishes the immutable source baseline before delivery setup. // publishInitialAppBranches establishes the immutable source baseline before delivery setup.
func publishInitialAppBranches(manager onboardingRepoManager, sourceDir, targetURL, sourceBranch, targetBranch, productionBranch string) error { func publishInitialAppBranches(manager onboardingRepoManager, sourceDir, targetURL, sourceBranch, targetBranch, productionBranch string) error {
sourceRevision, err := forgejo.BranchRevision(sourceDir, sourceBranch) sourceRevision, err := forgejo.BranchRevision(sourceDir, sourceBranch)
@ -150,7 +157,11 @@ func publishInitialAppBranches(manager onboardingRepoManager, sourceDir, targetU
// RegisterAppInCluster writes only the managed Flux registration for one app. // RegisterAppInCluster writes only the managed Flux registration for one app.
func RegisterAppInCluster(dir string, cfg config.Config) error { func RegisterAppInCluster(dir string, cfg config.Config) error {
content, err := renderAppRegistration(cfg) return registerAppInCluster(dir, cfg, hasRuntimeSecretGrant(cfg))
}
func registerAppInCluster(dir string, cfg config.Config, hasAddons bool) error {
content, err := renderAppRegistrationWithAddons(cfg, hasAddons)
if err != nil { if err != nil {
return err return err
} }
@ -208,6 +219,10 @@ func RegisterAppInCluster(dir string, cfg config.Config) error {
} }
func renderAppRegistration(cfg config.Config) ([]byte, error) { func renderAppRegistration(cfg config.Config) ([]byte, error) {
return renderAppRegistrationWithAddons(cfg, hasRuntimeSecretGrant(cfg))
}
func renderAppRegistrationWithAddons(cfg config.Config, hasAddons bool) ([]byte, error) {
if err := config.ValidateDelivery(cfg); err != nil { if err := config.ValidateDelivery(cfg); err != nil {
return nil, err return nil, err
} }
@ -242,15 +257,15 @@ spec:
dependsOn: dependsOn:
- name: tekton-catalog - name: tekton-catalog
sourceRef: sourceRef:
kind: GitRepository kind: GitRepository
name: %s name: %s
`, cfg.Delivery.AppName, cfg.Delivery.AppRepoURL, branch, cfg.Delivery.AppName, cfg.Delivery.AppName) `, cfg.Delivery.AppName, cfg.Delivery.AppRepoURL, branch, cfg.Delivery.AppName, cfg.Delivery.AppName)
if hasRuntimeSecretGrant(cfg) { if hasAddons {
content += fmt.Sprintf(`--- content += fmt.Sprintf(`---
apiVersion: kustomize.toolkit.fluxcd.io/v1 apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization kind: Kustomization
metadata: metadata:
name: %s-secrets name: %s-addons
namespace: flux-system namespace: flux-system
spec: spec:
interval: 5m interval: 5m

View file

@ -98,6 +98,17 @@ func TestRegisterAppInClusterRendersManagedFluxSource(t *testing.T) {
t.Fatalf("Kustomization %s does not include %s: %q, %v", path, resource, content, err) t.Fatalf("Kustomization %s does not include %s: %q, %v", path, resource, content, err)
} }
} }
decoder := yaml.NewDecoder(bytes.NewReader(registration))
for {
var document yaml.Node
err := decoder.Decode(&document)
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("registration YAML: %v", err)
}
}
if err := os.WriteFile(filepath.Join(tektonDir, "apps", "web-ui.yaml"), bytes.ReplaceAll(registration, []byte("\n"), []byte("\r\n")), 0644); err != nil { if err := os.WriteFile(filepath.Join(tektonDir, "apps", "web-ui.yaml"), bytes.ReplaceAll(registration, []byte("\n"), []byte("\r\n")), 0644); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -134,11 +145,34 @@ func TestGenerateAppSecretAccessRendersOnlyDeclaredRuntimeSecrets(t *testing.T)
t.Fatalf("secret Kustomization = %q, %v", kustomization, err) t.Fatalf("secret Kustomization = %q, %v", kustomization, err)
} }
registration, err := renderAppRegistration(cfg) registration, err := renderAppRegistration(cfg)
if err != nil || !strings.Contains(string(registration), "name: web-ui-secrets") || !strings.Contains(string(registration), "path: ./.maidn") { if err != nil || !strings.Contains(string(registration), "name: web-ui-addons") || !strings.Contains(string(registration), "path: ./.maidn") {
t.Fatalf("secret registration = %q, %v", registration, err) t.Fatalf("secret registration = %q, %v", registration, err)
} }
} }
func TestRenderAppRegistrationIncludesSourceAddons(t *testing.T) {
registration, err := renderAppRegistrationWithAddons(onboardingConfig(), true)
if err != nil || !strings.Contains(string(registration), "name: web-ui-addons") || !strings.Contains(string(registration), "path: ./.maidn") || !strings.Contains(string(registration), "external-secrets-config") {
t.Fatalf("addon registration = %q, %v", registration, err)
}
}
func TestHasMaidnAddons(t *testing.T) {
dir := t.TempDir()
if hasAddons, err := hasMaidnAddons(dir); err != nil || hasAddons {
t.Fatalf("absent add-ons = %t, %v", hasAddons, err)
}
if err := os.Mkdir(filepath.Join(dir, ".maidn"), 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, ".maidn", "kustomization.yaml"), []byte("apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n"), 0644); err != nil {
t.Fatal(err)
}
if hasAddons, err := hasMaidnAddons(dir); err != nil || !hasAddons {
t.Fatalf("present add-ons = %t, %v", hasAddons, err)
}
}
func TestPublishInitialAppBranchesCreatesAndPreservesProduction(t *testing.T) { func TestPublishInitialAppBranchesCreatesAndPreservesProduction(t *testing.T) {
source := filepath.Join(t.TempDir(), "source") source := filepath.Join(t.TempDir(), "source")
target := filepath.Join(t.TempDir(), "target.git") target := filepath.Join(t.TempDir(), "target.git")

View file

@ -623,14 +623,11 @@ func (rm *RepoManager) PublishRepositoryPullRequest(repo, title, branch, base st
if !open { if !open {
return false, nil return false, nil
} }
return true, rm.MergePullRequest(repo, branch) return true, nil
} }
if err := rm.EnsurePullRequest(repo, title, branch, base); err != nil { if err := rm.EnsurePullRequest(repo, title, branch, base); err != nil {
return false, err return false, err
} }
if err := rm.MergePullRequest(repo, branch); err != nil {
return false, err
}
return true, nil return true, nil
} }