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
31 changes: 28 additions & 3 deletions build_test.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,9 +82,14 @@ func TestBuildEmitsRecipeToOutputDirectory(t *testing.T) {
require.FileExists(t, filepath.Join(outputDirectory, "migrations", "1_create_table.up.sql"))
require.FileExists(t, filepath.Join(outputDirectory, "migrations", "1_create_table.down.sql"))

// runtime-access.sql the Dockerfile COPYs must sit at the context root beside
// migrations/, not inside the builder/ recipe-metadata directory the CLI drops
// from the staged build context — otherwise the COPY fails to resolve.
// The CLI resolves the recipe's "." context to the service directory, so the
// runtime-access.sql the Dockerfile COPYs root-relative must sit at the service
// directory root beside the committed migrations/ — otherwise the COPY the CLI
// runs against that context fails to resolve.
require.FileExists(t, filepath.Join(builder.Location, "runtime-access.sql"))

// It is also rendered into the recipe tree (never under builder/) so the emitted
// artifact stays a self-contained context a consumer can build directly.
require.FileExists(t, filepath.Join(outputDirectory, "runtime-access.sql"))
require.NoFileExists(t, filepath.Join(outputDirectory, "builder", "runtime-access.sql"))

Expand All@@ -93,6 +98,26 @@ func TestBuildEmitsRecipeToOutputDirectory(t *testing.T) {
require.NoError(t, services.VerifyDockerBuildPlan(outputDirectory, plan))
}

// TestFactoryScaffoldsGitignoreForGeneratedRuntimeAccess covers the file the
// build writes into the service directory on every run: Create must scaffold a
// .gitignore that keeps the generated runtime-access.sql out of version control,
// so a consumer's `codefly build service` does not leave untracked noise beside
// the committed migrations/. This also guards the `all:` embed — a plain
// //go:embed drops the .gitignore dotfile and the render would silently produce
// nothing.
func TestFactoryScaffoldsGitignoreForGeneratedRuntimeAccess(t *testing.T) {
ctx := context.Background()
builder := newBuildTestBuilder(t)

require.NoError(t, builder.Templates(ctx,
create{DatabaseName: "test", TableName: "postgres"},
services.WithFactory(factoryFS)))

data, err := os.ReadFile(filepath.Join(builder.Location, ".gitignore"))
require.NoError(t, err, "Create must scaffold a .gitignore at the service root")
require.Contains(t, string(data), "/runtime-access.sql")
}

func TestBuildRecipeOmitsMigrationsWhenDisabled(t *testing.T) {
ctx := context.Background()
builder := newBuildTestBuilder(t)
Expand Down
30 changes: 20 additions & 10 deletions builder.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -189,12 +189,14 @@ func (s *Builder) Build(ctx context.Context, req *builderv0.BuildRequest) (*buil
return s.Builder.BuildResponse()
}

// buildRecipe renders the bootstrap image's Dockerfile and build context into the
// caller-owned output directory and returns a reproducible build plan instead of
// running docker itself. The CLI builds the emitted recipe multi-arch and pushes
// a manifest list, so a consumer can rebuild the image without the agent
// toolchain. The context is the recipe tree itself: the rendered builder/ files
// plus the migrations the image applies at bootstrap.
// buildRecipe renders the bootstrap image's Dockerfile into the caller-owned
// output directory and returns a reproducible build plan instead of running
// docker itself. The CLI builds the emitted recipe multi-arch and pushes a
// manifest list, so a consumer can rebuild the image without the agent toolchain.
// The CLI resolves the recipe's "." context to the service directory (s.Location),
// not the recipe tree, so the files the Dockerfile COPYs must be staged there:
// migrations/ is already committed and runtime-access.sql is rendered in below.
// They are mirrored into the recipe tree so it also builds standalone.
func (s *Builder) buildRecipe(ctx context.Context, outputDirectory string, img *resources.DockerImage, docker DockerTemplating) (*builderv0.BuildResponse, error) {
// The plan inventories the whole output directory and the recipe context is
// its root, so any pre-existing content the caller left here would be
Expand All@@ -208,9 +210,15 @@ func (s *Builder) buildRecipe(ctx context.Context, outputDirectory string, img *
return s.Builder.BuildError(err)
}

// runtime-access.sql is a build-context file the Dockerfile COPYs, so it lives
// at the context root beside migrations/ — not inside builder/, which the CLI
// treats as recipe metadata and drops from the staged build context.
// The Dockerfile COPYs runtime-access.sql from the build context root. The CLI
// resolves the recipe's "." context to the service directory (s.Location) — not
// to the recipe tree — so render it there, beside the already-committed
// migrations/, for the build the CLI runs to resolve the COPY. Also render it
// into the recipe tree so the emitted artifact stays a self-contained context a
// consumer can build directly, mirroring how migrations/ lives in both places.
if err := s.renderRuntimeAccess(ctx, docker, s.Location); err != nil {
return s.Builder.BuildError(err)
}
if err := s.renderRuntimeAccess(ctx, docker, outputDirectory); err != nil {
return s.Builder.BuildError(err)
}
Expand DownExpand Up@@ -541,7 +549,9 @@ func (s *Builder) Communicate(stream builderv0.Builder_CommunicateServer) error
return err
}

//go:embed templates/factory
// all: so the scaffolded .gitignore (a dotfile go:embed skips by default) is
// carried into the factory tree and rendered into new services.
//go:embed all:templates/factory
var factoryFS embed.FS

//go:embed templates/builder
Expand Down
4 changes: 4 additions & 0 deletions templates/factory/.gitignore.tmpl
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# runtime-access.sql is re-rendered into the service directory on every
# `codefly build` — it is the bootstrap image's build context, generated and
# never hand-edited. Keep it out of version control.
/runtime-access.sql
Loading