Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/run/launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,7 @@ func (cluster *launcher) renderDelivery(ctx context.Context, deliverer *gitops.N
}
services = append(services, service)
}
if early {
if early && bundle == nil {
// Deferral removes a page. Keep a previously published payload during
// unrelated partial startup instead of accidentally unpublishing it.
if previous := cluster.delivered[module]; previous != nil && previous.Frontend != nil {
Expand Down
35 changes: 23 additions & 12 deletions cmd/run/launcher_admission.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,6 @@ func (cluster *launcher) beginRuntimeStart(ctx context.Context, service *resourc
// Failed Start leaves an admitted but unready workload, never a terminal
// withdrawal; normal run teardown stops credential refresh.
func (cluster *launcher) admitSelected(ctx context.Context) error {
if err := cluster.acquireDelivery(ctx); err != nil {
return err
}
defer cluster.delivery.Release(1)
// One snapshot of the selection decides both what is admitted and which
// binding targets are part of the run.
modules, selected := map[string]bool{}, map[string]bool{}
Expand All @@ -100,6 +96,27 @@ func (cluster *launcher) admitSelected(ctx context.Context) error {
names = append(names, module)
}
sort.Strings(names)
// A declared page is part of what a module is: the authority admits a
// module's roles only beside its page, so admission names the page's
// bundle as a ready delivery does. It is built here, before the delivery
// lock, so a build delays no other module's delivery; the ready delivery
// reuses the bundle retained for unchanged inputs.
loadedModules := make([]*resources.Module, len(names))
bundles := make([]*solutionhost.FrontendPayload, len(names))
for n, module := range names {
loaded, err := cluster.composed().LoadModuleFromName(ctx, module)
if err != nil {
return err
}
if bundles[n], err = cluster.frontendBundle(ctx, loaded); err != nil {
return err
}
loadedModules[n] = loaded
}
if err := cluster.acquireDelivery(ctx); err != nil {
return err
}
defer cluster.delivery.Release(1)
if err := cluster.checkRoutingSnapshot(ctx); err != nil {
return err
}
Expand All @@ -108,14 +125,8 @@ func (cluster *launcher) admitSelected(ctx context.Context) error {
return err
}
manifests := make([]*solutionhost.ModuleManifest, 0, len(names))
for _, module := range names {
loaded, err := cluster.composed().LoadModuleFromName(ctx, module)
if err != nil {
return err
}
// Admission builds no page: the bundle a ready delivery builds, outside
// the delivery lock, publishes it.
manifest, err := cluster.renderDelivery(ctx, deliverer, loaded, true, nil)
for n, module := range names {
manifest, err := cluster.renderDelivery(ctx, deliverer, loadedModules[n], true, bundles[n])
if err != nil {
return err
}
Expand Down
47 changes: 47 additions & 0 deletions cmd/run/launcher_admission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/codefly-dev/cli/pkg/gitops"
"github.com/codefly-dev/cli/pkg/orchestration"
"github.com/codefly-dev/cli/pkg/run/identity"
"github.com/codefly-dev/cli/pkg/solutionrun"
basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0"
"github.com/codefly-dev/core/resources"
"github.com/codefly-dev/core/solutionhost"
Expand Down Expand Up @@ -490,3 +491,49 @@ func TestFailedExternalPreparationReleasesRefresh(t *testing.T) {
_, projected := cluster.vouch.Projected("alpha/api")
require.False(t, projected, "failed preparation removes refresh membership")
}

// A declared page is part of what a module is: the authority admits a module's
// roles only beside its page, so admission names the page's bundle, built
// before the first consumer starts, rather than deferring it to the ready
// delivery. The ready delivery reuses the bundle retained for those inputs.
func TestAdmissionCarriesADeclaredPage(t *testing.T) {
cluster, peer := admissionLauncher(t)
dir := filepath.Join(cluster.composed().Dir(), "modules", "alpha")
moduleFile := filepath.Join(dir, resources.ModuleConfigurationName)
body, err := os.ReadFile(moduleFile)
require.NoError(t, err)
require.NoError(t, os.WriteFile(moduleFile, append([]byte("service-entry: api\n"), body...), 0o600))
require.NoError(t, os.WriteFile(filepath.Join(dir, "services", "api", solutionrun.FrontendFileName), []byte("title: Alpha\norder: 1\nexposed_module: ./Page\nhost_contract: 1\n"), 0o600))
remote := filepath.Join(dir, "services", "fe-remote")
require.NoError(t, os.MkdirAll(remote, 0o755))
for name, content := range map[string]string{"package.json": `{"scripts":{"build":"owner-build"}}`, "package-lock.json": `{"lockfileVersion":3}`, "Page.tsx": "page"} {
require.NoError(t, os.WriteFile(filepath.Join(remote, name), []byte(content), 0o600))
}
bin := t.TempDir()
t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH"))
builds := filepath.Join(t.TempDir(), "builds")
fakeTool(t, bin, "npm", `case "$*" in
ci) mkdir -p node_modules ;;
"run build") echo build >> `+builds+` && mkdir -p dist && printf '{}' > dist/mf-manifest.json ;;
esac
`)
workspace, err := resources.LoadWorkspaceFromDir(t.Context(), cluster.composed().Dir())
require.NoError(t, err)
cluster.workspace = workspace

require.NoError(t, cluster.admitSelected(t.Context()))
pages := map[string]*solutionhost.FrontendPayload{}
for _, m := range peer.snapshots() {
if m.Frontend != nil {
pages[m.Alias] = m.Frontend.Payload
}
}
require.Len(t, pages, 1, "only the module that declares a page is admitted with one")
require.NotNil(t, pages["alpha"], "admission names the page's bundle")
require.NotEmpty(t, pages["alpha"].Digest)

require.NoError(t, cluster.admitSelected(t.Context()))
ran, err := os.ReadFile(builds)
require.NoError(t, err)
require.Equal(t, 1, strings.Count(string(ran), "build"), "unchanged inputs reuse the retained bundle")
}
Loading