feat: select runtime delivery builds
This commit is contained in:
parent
7df7d03dbe
commit
08851c6324
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
var freshConfigPath, freshOrganization, onboardConfigPath, onboardFrom string
|
||||
var onboardAppName, onboardAppRepoURL, onboardImageRepository string
|
||||
var onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy string
|
||||
var freshCreateOrganization, freshEnableDelivery, freshYes bool
|
||||
var freshMode string
|
||||
|
||||
|
|
@ -54,6 +54,7 @@ func init() {
|
|||
appOnboardCmd.Flags().StringVar(&onboardAppName, "app-name", "", "Application name 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(&onboardBuildStrategy, "build-strategy", "", "Build strategy override: static or runtime")
|
||||
_ = appOnboardCmd.MarkFlagRequired("config")
|
||||
_ = appOnboardCmd.MarkFlagRequired("from")
|
||||
}
|
||||
|
|
@ -87,6 +88,9 @@ func runAppOnboard(_ *cobra.Command, _ []string) error {
|
|||
if onboardImageRepository != "" {
|
||||
cfg.Delivery.ImageRepository = onboardImageRepository
|
||||
}
|
||||
if onboardBuildStrategy != "" {
|
||||
cfg.Delivery.BuildStrategy = onboardBuildStrategy
|
||||
}
|
||||
cfg, err = resolveAppOnboarding(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -65,11 +65,11 @@ func TestBootstrapInitAppliesFluxDefaultsBeforeFreshValidation(t *testing.T) {
|
|||
func TestAppOnboardValidatesConfigBeforeExternalWork(t *testing.T) {
|
||||
originalConfig, originalResolve, originalOnboard := loadAppOnboardConfig, resolveAppOnboarding, onboardApp
|
||||
originalConfigPath, originalFrom := onboardConfigPath, onboardFrom
|
||||
originalName, originalRepo, originalImage := onboardAppName, onboardAppRepoURL, onboardImageRepository
|
||||
originalName, originalRepo, originalImage, originalBuildStrategy := onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy
|
||||
t.Cleanup(func() {
|
||||
loadAppOnboardConfig, resolveAppOnboarding, onboardApp = originalConfig, originalResolve, originalOnboard
|
||||
onboardConfigPath, onboardFrom = originalConfigPath, originalFrom
|
||||
onboardAppName, onboardAppRepoURL, onboardImageRepository = originalName, originalRepo, originalImage
|
||||
onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy = originalName, originalRepo, originalImage, originalBuildStrategy
|
||||
})
|
||||
loadAppOnboardConfig = func(string) (config.Config, error) { return config.Config{}, nil }
|
||||
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
|
||||
}
|
||||
onboardConfigPath, onboardFrom = "private.yaml", "app-checkout"
|
||||
onboardAppName, onboardAppRepoURL, onboardImageRepository = "", "", ""
|
||||
onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy = "", "", "", ""
|
||||
if err := runAppOnboard(nil, nil); err == nil {
|
||||
t.Fatal("onboarding accepted invalid configuration")
|
||||
}
|
||||
|
|
@ -87,25 +87,25 @@ func TestAppOnboardValidatesConfigBeforeExternalWork(t *testing.T) {
|
|||
func TestAppOnboardPassesOnlyValidatedConfigAndCheckout(t *testing.T) {
|
||||
originalConfig, originalResolve, originalOnboard := loadAppOnboardConfig, resolveAppOnboarding, onboardApp
|
||||
originalConfigPath, originalFrom := onboardConfigPath, onboardFrom
|
||||
originalName, originalRepo, originalImage := onboardAppName, onboardAppRepoURL, onboardImageRepository
|
||||
originalName, originalRepo, originalImage, originalBuildStrategy := onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy
|
||||
t.Cleanup(func() {
|
||||
loadAppOnboardConfig, resolveAppOnboarding, onboardApp = originalConfig, originalResolve, originalOnboard
|
||||
onboardConfigPath, onboardFrom = originalConfigPath, originalFrom
|
||||
onboardAppName, onboardAppRepoURL, onboardImageRepository = originalName, originalRepo, originalImage
|
||||
onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy = originalName, originalRepo, originalImage, originalBuildStrategy
|
||||
})
|
||||
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 }
|
||||
resolveAppOnboarding = func(got config.Config) (config.Config, error) { return got, nil }
|
||||
calls := 0
|
||||
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" {
|
||||
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" {
|
||||
t.Fatal("onboarding used the wrong checkout or config")
|
||||
}
|
||||
calls++
|
||||
return nil
|
||||
}
|
||||
onboardConfigPath, onboardFrom = "private.yaml", "app-checkout"
|
||||
onboardAppName, onboardAppRepoURL, onboardImageRepository = "fixture", "https://git.example.test/new-org/fixture.git", "registry.example.test/new-org/fixture"
|
||||
onboardAppName, onboardAppRepoURL, onboardImageRepository, onboardBuildStrategy = "fixture", "https://git.example.test/new-org/fixture.git", "registry.example.test/new-org/fixture", "runtime"
|
||||
if err := runAppOnboard(nil, nil); err != nil || calls != 1 {
|
||||
t.Fatalf("runAppOnboard() = %v, calls = %d", err, calls)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -635,7 +635,7 @@ spec:
|
|||
operator: notin
|
||||
values: [closed]
|
||||
taskRef:
|
||||
name: maidn-node-static-image
|
||||
name: {{ if eq .BuildStrategy "static" }}maidn-node-static-image{{ else }}maidn-node-runtime-image{{ end }}
|
||||
params:
|
||||
- name: url
|
||||
value: {{ quote .AppRepoURL }}
|
||||
|
|
@ -643,10 +643,12 @@ spec:
|
|||
value: $(params.git-revision)
|
||||
- name: image
|
||||
value: $(params.image)
|
||||
{{ if eq .BuildStrategy "static" }}
|
||||
- name: output-directory
|
||||
value: {{ quote .BuildOutputDirectory }}
|
||||
- name: build-configuration
|
||||
value: {{ quote .BuildConfiguration }}
|
||||
{{ end }}
|
||||
- name: update-preview
|
||||
runAfter: [build-layer]
|
||||
when:
|
||||
|
|
|
|||
|
|
@ -624,6 +624,7 @@ type appDeliveryTemplateConfig struct {
|
|||
AppRepoRef string
|
||||
ProductionBranch string
|
||||
ImageRepository string
|
||||
BuildStrategy string
|
||||
BuildOutputDirectory string
|
||||
BuildConfiguration string
|
||||
ForgejoBaseURL string
|
||||
|
|
@ -693,6 +694,9 @@ func renderAppDelivery(cfg config.Config) ([]byte, error) {
|
|||
if !deliveryAppName.MatchString(cfg.Delivery.AppName) {
|
||||
return nil, errors.New("delivery appName must be a lowercase DNS label")
|
||||
}
|
||||
if cfg.Delivery.BuildStrategy != "static" && cfg.Delivery.BuildStrategy != "runtime" {
|
||||
return nil, errors.New("delivery buildStrategy must be static or runtime")
|
||||
}
|
||||
appRepository, err := deliveryRepository(cfg.Git.BaseURL, cfg.Delivery.AppRepoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -704,7 +708,7 @@ func renderAppDelivery(cfg config.Config) ([]byte, error) {
|
|||
values := appDeliveryTemplateConfig{
|
||||
AppName: cfg.Delivery.AppName, AppRepository: appRepository, AppRepoURL: cfg.Delivery.AppRepoURL,
|
||||
AppRepoRef: cfg.Delivery.AppRepoRef, ProductionBranch: cfg.Delivery.ProductionBranch, ImageRepository: cfg.Delivery.ImageRepository,
|
||||
BuildOutputDirectory: cfg.Delivery.BuildOutputDirectory, BuildConfiguration: cfg.Delivery.BuildConfiguration,
|
||||
BuildStrategy: cfg.Delivery.BuildStrategy, BuildOutputDirectory: cfg.Delivery.BuildOutputDirectory, BuildConfiguration: cfg.Delivery.BuildConfiguration,
|
||||
ForgejoBaseURL: origin, ForgejoOwner: cfg.Git.Owner, ManifestsURL: forgejo.CloneURL(origin, cfg.Git.Owner, cfg.Flux.ManifestsRepo), ManifestsRepo: cfg.Flux.ManifestsRepo, ManifestsBranch: cfg.Flux.Branch,
|
||||
}
|
||||
for name, value := range map[string]string{"appRepository": values.AppRepository, "appRepoUrl": values.AppRepoURL, "appRepoRef": values.AppRepoRef, "productionBranch": values.ProductionBranch, "imageRepository": values.ImageRepository, "buildOutputDirectory": values.BuildOutputDirectory, "buildConfiguration": values.BuildConfiguration, "forgejoBaseUrl": values.ForgejoBaseURL, "forgejoOwner": values.ForgejoOwner, "manifestsUrl": values.ManifestsURL, "manifestsRepo": values.ManifestsRepo, "manifestsBranch": values.ManifestsBranch} {
|
||||
|
|
|
|||
|
|
@ -112,13 +112,13 @@ func TestGeneratedDeliveryUsesCombinedStaticBuildArtifactContract(t *testing.T)
|
|||
cfg := config.Config{
|
||||
Git: config.GitConfig{BaseURL: "https://git.example.test", Owner: "platform"},
|
||||
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/web-ui", BuildConfiguration: "production"},
|
||||
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", BuildStrategy: "static", BuildOutputDirectory: "dist/web-ui", BuildConfiguration: "production"},
|
||||
}
|
||||
content, err := renderAppDelivery(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, expected := range []string{"name: build-layer", "maidn-node-static-image", "name: url\n value: \"https://git.example.test/apps/web-ui.git\"", "name: revision\n value: $(params.git-revision)", "name: image\n value: $(params.image)", "runAfter: [build-layer]", "maidn-preview-orphan-reconciler", "valid_pr_number()", "valid_commit()", "values: [promotion]", "values: [\"production\"]", "cmp -s \"$expected_marker\" \"$marker\"", "values: [closed]"} {
|
||||
for _, expected := range []string{"name: build-layer", "maidn-node-static-image", "name: url\n value: \"https://git.example.test/apps/web-ui.git\"", "name: revision\n value: $(params.git-revision)", "name: image\n value: $(params.image)", "name: output-directory\n value: \"dist/web-ui\"", "name: build-configuration\n value: \"production\"", "runAfter: [build-layer]", "maidn-preview-orphan-reconciler", "valid_pr_number()", "valid_commit()", "values: [promotion]", "values: [\"production\"]", "cmp -s \"$expected_marker\" \"$marker\"", "values: [closed]"} {
|
||||
if !strings.Contains(string(content), expected) {
|
||||
t.Fatalf("generated delivery does not contain %q", expected)
|
||||
}
|
||||
|
|
@ -130,11 +130,33 @@ func TestGeneratedDeliveryUsesCombinedStaticBuildArtifactContract(t *testing.T)
|
|||
}
|
||||
}
|
||||
|
||||
func TestGeneratedDeliveryUsesRuntimeImageWithoutStaticParameters(t *testing.T) {
|
||||
cfg := config.Config{
|
||||
Git: config.GitConfig{BaseURL: "https://git.example.test", Owner: "platform"},
|
||||
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", BuildStrategy: "runtime", BuildOutputDirectory: "dist/web-ui", BuildConfiguration: "production"},
|
||||
}
|
||||
content, err := renderAppDelivery(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, expected := range []string{"maidn-node-runtime-image", "name: url\n value: \"https://git.example.test/apps/web-ui.git\"", "name: revision\n value: $(params.git-revision)", "name: image\n value: $(params.image)"} {
|
||||
if !strings.Contains(string(content), expected) {
|
||||
t.Fatalf("generated runtime delivery does not contain %q", expected)
|
||||
}
|
||||
}
|
||||
for _, unexpected := range []string{"maidn-node-static-image", "name: output-directory", "name: build-configuration"} {
|
||||
if strings.Contains(string(content), unexpected) {
|
||||
t.Fatalf("generated runtime delivery contains static-only %q", unexpected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratedDeliveryInitializesStagingAndPromotesByPullRequest(t *testing.T) {
|
||||
cfg := config.Config{
|
||||
Git: config.GitConfig{BaseURL: "https://git.example.test", Owner: "platform"},
|
||||
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"},
|
||||
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", BuildStrategy: "static", BuildOutputDirectory: "dist", BuildConfiguration: "production"},
|
||||
}
|
||||
content, err := renderAppDelivery(cfg)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ func onboardingConfig() config.Config {
|
|||
Flux: config.FluxConfig{Branch: "main", RepoName: "cluster", ManifestsRepo: "manifests", ClusterDomain: "example.test"},
|
||||
Delivery: config.DeliveryConfig{
|
||||
AppName: "web-ui", AppRepoURL: "https://git.example.test/test-org-2/web-ui.git", AppRepoRef: "main", ProductionBranch: "production",
|
||||
ImageRepository: "registry.example.test/test-org-2/web-ui", BuildOutputDirectory: "dist", BuildConfiguration: "production",
|
||||
ImageRepository: "registry.example.test/test-org-2/web-ui", BuildStrategy: "static", BuildOutputDirectory: "dist", BuildConfiguration: "production",
|
||||
WebhookHostname: "tekton.example.test", WebhookPath: "/",
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,6 +215,9 @@ func validateRepositoryURL(value string) error {
|
|||
}
|
||||
|
||||
func applyDefaults(cfg *Config) {
|
||||
if cfg.Delivery.BuildStrategy == "" {
|
||||
cfg.Delivery.BuildStrategy = "static"
|
||||
}
|
||||
if cfg.ClusterID == "" {
|
||||
cfg.ClusterID = cfg.Talos.Cluster.Name
|
||||
}
|
||||
|
|
@ -317,6 +320,9 @@ func applyDefaults(cfg *Config) {
|
|||
}
|
||||
|
||||
func applyDeliveryDefaults(cfg *Config) {
|
||||
if cfg.Delivery.BuildStrategy == "" {
|
||||
cfg.Delivery.BuildStrategy = "static"
|
||||
}
|
||||
if cfg.Delivery.AppRepoRef == "" {
|
||||
cfg.Delivery.AppRepoRef = cfg.Flux.Branch
|
||||
}
|
||||
|
|
@ -542,6 +548,9 @@ func ValidateDelivery(cfg Config) error {
|
|||
if !regexp.MustCompile(`^[a-z0-9]([a-z0-9-]{0,45}[a-z0-9])?$`).MatchString(cfg.Delivery.AppName) {
|
||||
return errors.New("delivery appName must be a lowercase DNS label of at most 47 characters")
|
||||
}
|
||||
if cfg.Delivery.BuildStrategy != "static" && cfg.Delivery.BuildStrategy != "runtime" {
|
||||
return errors.New("delivery buildStrategy must be static or runtime")
|
||||
}
|
||||
if cfg.Delivery.ProductionBranch == cfg.Delivery.AppRepoRef || !validDeliveryBranch(cfg.Delivery.ProductionBranch) {
|
||||
return errors.New("delivery productionBranch must be a valid branch distinct from appRepoRef")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ func validConfig(t *testing.T) Config {
|
|||
Templates: TemplateConfig{TalosRepoURL: "https://git.example.test/talos.git", TalosRepoRef: "main", CICDRepoURL: "https://git.example.test/template.git", CICDRepoRef: "main", ManifestsRepoURL: "https://git.example.test/manifests.git", ManifestsRepoRef: "main"},
|
||||
Cilium: CiliumConfig{TrafficInterface: "eth1", LoadBalancerStart: "192.168.45.19", LoadBalancerEnd: "192.168.45.30"},
|
||||
DemocraticCSI: DemocraticCSIConfig{TrueNASAPIKey: "api-key", TrueNASHost: "truenas.example.test", TargetPortal: "truenas.example.test:3260", ShareHost: "truenas.example.test", DatasetParentNFS: "pool/kubernetes/nfs/v", DatasetSnapshotsNFS: "pool/kubernetes/nfs/s", AllowedNetworks: "192.168.45.0/24", NameSuffix: "-test", PortalGroup: "1", InitiatorGroup: "1"},
|
||||
Delivery: DeliveryConfig{AppName: "web-ui", AppRepoURL: "https://git.example.test/test-org/web-ui.git", AppRepoRef: "main", ProductionBranch: "production", ImageRepository: "registry.example.test/test-org/web-ui", BuildOutputDirectory: "dist", BuildConfiguration: "production", WebhookHostname: "tekton.example.test", WebhookPath: "/"},
|
||||
Delivery: DeliveryConfig{AppName: "web-ui", AppRepoURL: "https://git.example.test/test-org/web-ui.git", AppRepoRef: "main", ProductionBranch: "production", ImageRepository: "registry.example.test/test-org/web-ui", BuildStrategy: "static", BuildOutputDirectory: "dist", BuildConfiguration: "production", WebhookHostname: "tekton.example.test", WebhookPath: "/"},
|
||||
Talos: TalosConfig{
|
||||
RepoDirName: "talos", TerraformDir: "terraform", GeneratedDir: "generated", ConfigFileName: "terraform.tfvars",
|
||||
Proxmox: TalosProxmoxConfig{APIURL: "https://proxmox.example.test:8006", APITokenID: "id", APITokenSecret: "secret"},
|
||||
|
|
@ -64,6 +64,7 @@ func TestResolveDeliveryAppliesDefaultsOnlyForExplicitAppOperations(t *testing.T
|
|||
cfg.Delivery.AppRepoRef = ""
|
||||
cfg.Delivery.BuildOutputDirectory = ""
|
||||
cfg.Delivery.BuildConfiguration = ""
|
||||
cfg.Delivery.BuildStrategy = ""
|
||||
cfg.Delivery.WebhookHostname = ""
|
||||
cfg.Delivery.WebhookPath = ""
|
||||
|
||||
|
|
@ -78,11 +79,25 @@ func TestResolveDeliveryAppliesDefaultsOnlyForExplicitAppOperations(t *testing.T
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !delivery.Delivery.Configured() || delivery.Delivery.AppRepoRef != "main" || delivery.Delivery.BuildOutputDirectory != "dist" || delivery.Delivery.BuildConfiguration != "production" || delivery.Delivery.WebhookURL() != "https://tekton.example.test/" {
|
||||
if !delivery.Delivery.Configured() || delivery.Delivery.BuildStrategy != "static" || delivery.Delivery.AppRepoRef != "main" || delivery.Delivery.BuildOutputDirectory != "dist" || delivery.Delivery.BuildConfiguration != "production" || delivery.Delivery.WebhookURL() != "https://tekton.example.test/" {
|
||||
t.Fatalf("ResolveDelivery() did not apply the complete delivery contract: %#v", delivery.Delivery)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDeliveryBuildStrategy(t *testing.T) {
|
||||
cfg := validConfig(t)
|
||||
for _, strategy := range []string{"static", "runtime"} {
|
||||
cfg.Delivery.BuildStrategy = strategy
|
||||
if err := ValidateDelivery(cfg); err != nil {
|
||||
t.Fatalf("ValidateDelivery() rejected %q: %v", strategy, err)
|
||||
}
|
||||
}
|
||||
cfg.Delivery.BuildStrategy = "container"
|
||||
if err := ValidateDelivery(cfg); err == nil || !strings.Contains(err.Error(), "buildStrategy") {
|
||||
t.Fatalf("ValidateDelivery() accepted invalid build strategy: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDeliveryRequiresCompleteConfig(t *testing.T) {
|
||||
cfg := validConfig(t)
|
||||
cfg.Delivery.ImageRepository = ""
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ type DeliveryConfig struct {
|
|||
AppRepoRef string `yaml:"appRepoRef"`
|
||||
ProductionBranch string `yaml:"productionBranch"`
|
||||
ImageRepository string `yaml:"imageRepository"`
|
||||
BuildStrategy string `yaml:"buildStrategy"`
|
||||
BuildOutputDirectory string `yaml:"buildOutputDirectory"`
|
||||
BuildConfiguration string `yaml:"buildConfiguration"`
|
||||
TektonDashboardURL string `yaml:"tektonDashboardUrl,omitempty"`
|
||||
|
|
|
|||
Loading…
Reference in a new issue