Skip to content

feat: add step.build_from_config — assemble self-contained workflow container image#162

Merged
intel352 merged 1 commit intomainfrom
feat/step-build-from-config
Feb 25, 2026
Merged

feat: add step.build_from_config — assemble self-contained workflow container image#162
intel352 merged 1 commit intomainfrom
feat/step-build-from-config

Conversation

@intel352
Copy link
Contributor

Summary

  • Adds step.build_from_config to the cicd plugin (Phase 5.1 roadmap item)
  • The step assembles a Docker image from a workflow config YAML file, a server binary, and optional named plugin binaries — no registry download required in this initial version
  • exec.Command is injected via a field on the step struct, enabling deterministic unit tests without running Docker

What it does

  1. Validates that config_file and server_binary exist on disk
  2. Creates a temp build context directory
  3. Copies config → config.yaml, server binary → server, and each plugin binary → plugins/<name>/<binary>
  4. Generates a Dockerfile:
    FROM <base_image>
    COPY server /server
    COPY config.yaml /app/config.yaml
    COPY plugins/ /app/data/plugins/   # only if plugins configured
    WORKDIR /app
    ENTRYPOINT ["/server"]
    CMD ["-config", "/app/config.yaml", "-data-dir", "/app/data"]
  5. Runs docker build -t <tag> <context_dir>
  6. Optionally runs docker push <tag> when push: true
  7. Returns image_tag and dockerfile_content as step outputs

Example config

- type: step.build_from_config
  config:
    config_file: "app.yaml"
    base_image: "gcr.io/distroless/static-debian12:nonroot"
    server_binary: "/usr/local/bin/workflow-server"
    tag: "my-app:latest"
    push: false
    plugins:
      - name: admin
        binary: "data/plugins/admin/admin"
      - name: bento
        binary: "data/plugins/bento/workflow-plugin-bento"

Test plan

  • go test ./module/ -run TestBuildFromConfig — 17 tests, all pass
  • go test ./plugins/cicd/... — 4 tests, all pass (count updated 12→13)
  • go build ./... — full build succeeds with no new errors

🤖 Generated with Claude Code

Implements step.build_from_config (Phase 5.1 roadmap) — a pipeline step
that assembles a self-contained Docker image from a workflow config YAML
file, a server binary, and optional plugin binaries.

- Creates a temp build context, copies config + server + plugin binaries
- Generates a Dockerfile with correct ENTRYPOINT/CMD for workflow server
- Executes docker build (and optional docker push) via exec.Command
- exec.Command is injectable for deterministic unit testing
- 17 tests cover factory validation, Dockerfile generation, error paths,
  push flag, plugin inclusion, and build context file layout
- Registers step.build_from_config in plugins/cicd manifest and factory map

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 25, 2026 05:39
@intel352 intel352 merged commit 273de57 into main Feb 25, 2026
16 checks passed
@intel352 intel352 deleted the feat/step-build-from-config branch February 25, 2026 05:39
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds step.build_from_config to the cicd plugin, implementing Phase 5.1 of the roadmap. The step assembles self-contained Docker images from workflow configuration YAML files by bundling the server binary, config file, and optional plugin binaries into a single container image. The implementation uses dependency injection for exec.CommandContext to enable deterministic unit testing without requiring Docker to be present during tests.

Changes:

  • Adds new BuildFromConfigStep implementation that validates inputs, creates a temporary build context, generates a Dockerfile, and executes docker build/push commands
  • Updates cicd plugin registration to include the new step type across descriptions, manifests, and factory maps
  • Provides comprehensive test coverage with 17 test cases covering factory validation, Dockerfile generation, execution paths, and error scenarios

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
module/pipeline_step_build_from_config.go Core implementation with factory, Execute method, Dockerfile generation, and docker command execution
module/pipeline_step_build_from_config_test.go Comprehensive test suite with 17 tests covering factory validation, execution paths, and build context layout
plugins/cicd/plugin.go Updated plugin registration, descriptions, and factory map to include step.build_from_config
plugins/cicd/plugin_test.go Updated test expectations to reflect 13 total step factories (was 12)

}
}

func TestBuildFromConfigStep_GenerateDockerfile_NoPLugins(t *testing.T) {
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in function name: "NoPLugins" should be "NoPlugins" (lowercase 'l').

Suggested change
func TestBuildFromConfigStep_GenerateDockerfile_NoPLugins(t *testing.T) {
func TestBuildFromConfigStep_GenerateDockerfile_NoPlugins(t *testing.T) {

Copilot uses AI. Check for mistakes.
Comment on lines +500 to +513
_, err = fmt.Fprintf(out, "")
if err != nil {
return err
}
_, err = out.Seek(0, 0)
if err != nil {
return err
}
f, err := os.Open(path) //nolint:gosec
if err != nil {
return err
}
defer f.Close()
_, copyErr := io.Copy(out, f)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copyDirRecursive helper function has unnecessary complexity. Lines 500-507 write an empty string and then seek back to position 0, which is redundant since the file was just created with O_TRUNC. Additionally, lines 508-513 open the same source file again (that was already opened at line 490) for copying. This can be simplified to directly use io.Copy from the already-opened 'in' file handle instead of reopening it as 'f'.

Suggested change
_, err = fmt.Fprintf(out, "")
if err != nil {
return err
}
_, err = out.Seek(0, 0)
if err != nil {
return err
}
f, err := os.Open(path) //nolint:gosec
if err != nil {
return err
}
defer f.Close()
_, copyErr := io.Copy(out, f)
_, copyErr := io.Copy(out, in)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants