Skip to content

feat(codebuild): add generic ImageBuild resource (builds + pushes an image from a supplied Dockerfile)#135

Open
JeroenSoeters wants to merge 9 commits into
mainfrom
clanker/PLA-254
Open

feat(codebuild): add generic ImageBuild resource (builds + pushes an image from a supplied Dockerfile)#135
JeroenSoeters wants to merge 9 commits into
mainfrom
clanker/PLA-254

Conversation

@JeroenSoeters

@JeroenSoeters JeroenSoeters commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a native AWS-plugin resource, AWS::CodeBuild::ImageBuild, that builds a container image from a supplied Dockerfile via AWS CodeBuild, pushes it to ECR, and exposes the pushed image's immutable digest reference as resolvables a downstream resource (an ECS TaskDefinition, a k8s Pod) can consume. This makes an image build a declarative step inside formae instead of an out-of-band docker build/push, so a consumer can pin to exactly the image that was produced.

  • A synthetic, imperative-during-apply resource — not a CloudControl passthrough. It reuses the plugin's existing async Create → InProgress + Status-poll → Success pattern, so it needs no new SDK primitive. Create validates its inputs and idempotently ensures an internal IAM service role + CodeBuild project (or uses a supplied serviceRoleArn), starts the build, and returns in-progress; Status reads the digest captured from the specific push once the build succeeds.
  • Inputs: dockerfile (built verbatim), ecrRepositoryUri (a resolvable from an AWS::ECR::Repository, which gains a repositoryUri resolvable here), imageTag, optional buildArgs, and build tuning (computeType, timeoutMinutes, buildEnvironmentImage, and a bring-your-own serviceRoleArn).
  • Outputs (via the Pkl res. accessor): imageRef (<repo>@sha256:…, the immutable primary reference and the resource identity), imageDigest, imageUri (<repo>:<tag>, a mutable convenience alias), and imageTag.
  • Rebuild gating. A buildConfigHash over the build-affecting inputs gates rebuilds: Create/Update rebuild only when that hash changes, when the recorded digest is absent from ECR, or when the declared tag no longer resolves to the recorded digest — otherwise they no-op and return the existing outputs. ecrRepositoryUri and imageTag are identity (createOnly, so changing them forces a replace); the Dockerfile, build args, and build tuning update in place.
  • Isolation & cleanup. The Dockerfile and build args are materialized in an isolated build context so their values never leak into the image, and inputs are validated before use. Delete stops any in-flight build, removes the image it pushed (scoped to its own tag, mirroring the S3 Object→Bucket pattern so a co-managed repository can be torn down), and deletes the internal project + role; a supplied bring-your-own role is never mutated or deleted.
  • v1 scope. The ECR target must be in the same account and region as the build; pruning digests orphaned by same-tag in-place rebuilds and pulling a private base image from a different repository are documented in code and deferred to a follow-up.

clanker-pel Bot and others added 4 commits July 1, 2026 07:48
…tom agent image

Add AWS::CodeBuild::AgentImage, a synthetic build-during-apply resource that
generates a Dockerfile and buildspec from a base image plus version-pinned
plugins, runs an AWS CodeBuild build, and exposes the pushed image's immutable
digest reference (imageRef/imageDigest) along with imageUri/imageTag as
resolvable outputs a consumer can pin to.

The build runs asynchronously via the existing Create->InProgress + Status-poll
pattern, so no new SDK primitive is needed. The CodeBuild project and IAM
service role are created and torn down internally (the role is overridable via
serviceRoleArn); rebuilds are gated by a build-config hash over the
build-affecting inputs so inconsequential edits do not trigger a rebuild.

Also expose repositoryUri (plus arn and repositoryName) as resolvables on
AWS::ECR::Repository so a repository can be wired into the build as its push
target.
The AgentImage build pushes an image into the target ECR repository, but
delete only removed the build project and service role, leaving the image
behind. A co-managed ECR repository can then not be torn down: its delete
fails because it still contains that image.

Setting emptyOnDelete on the repository does not help — it is a writeOnly
property and the CloudControl delete path carries only the identifier, so
the value never reaches the delete handler.

Mirror the S3 Object pattern: the resource that put content into a
container removes it on delete. AgentImage delete now removes the image it
pushed, scoped to exactly its own tag, so it never touches images pushed
by anything else. A missing image or repository is treated as success.
Make the AgentImage lifecycle robust on the update and delete paths:

- Update skips a rebuild only when the declared tag still resolves to the
  recorded digest, so a tag moved out of band is reconciled rather than
  reported as unchanged.
- Delete tolerates an already-gone CodeBuild project so a partially
  completed delete can be retried through image and role cleanup.
- Delete removes the internal service role only when it exists under the
  plugin's deterministic name, leaving a caller-supplied (BYO) role
  untouched and not failing teardown when the agent has no IAM access.
…resource

Rename AWS::CodeBuild::AgentImage to AWS::CodeBuild::ImageBuild and decouple
it from the formae agent. The resource now takes a literal `dockerfile` string
(plus optional `buildArgs`) and builds whatever Dockerfile it is handed,
instead of the former `baseImage` + `plugins` inputs that generated a
formae-agent Dockerfile (formae plugin install, /home/pel plugin cache
cleanup, chown /opt/pel, USER pel).

The agent Dockerfile composition moves to consumers; the AWS plugin no longer
references formae plugins, pelmgr, `formae plugin install`, or the `pel` user.

- Pkl: agentimage.pkl -> imagebuild.pkl; drop the Plugin subresource and
  PluginChannel typealias; add required `dockerfile` and optional `buildArgs`.
- Go: agentimage.go -> imagebuild.go; remove generateDockerfile(); the
  buildspec materializes the Dockerfile from DOCKERFILE_B64 verbatim and
  threads buildArgs through as repeated --build-arg flags via BUILD_ARGS_B64.
- buildConfigHash now hashes dockerfile + buildArgs (canonical) +
  generatorVersion + computeType + buildEnvironmentImage.
- All reusable orchestration is kept: CodeBuild project/role lifecycle,
  digest capture, async Create->Status, BYO-role handling, delete-empties-
  own-image, IAM policy derived from the buildspec.
- Fixture and unit tests updated to the new inputs/names.
@JeroenSoeters JeroenSoeters changed the title feat(codebuild): add AgentImage resource that builds and pushes a custom agent image feat(codebuild): add generic ImageBuild resource (builds + pushes an image from a supplied Dockerfile) Jul 1, 2026
clanker-pel Bot added 5 commits July 1, 2026 15:32
Require the ECR push target to be in the build project's own region and
reject a mismatch with a clear error: the CodeBuild project, its log
group, its IAM policy, and the ECR API clients all run in the target
region, so a cross-region repository would build but then fail to log
or to be read and deleted.

Read build args into the shell's positional parameters and pass them as
quoted --build-arg flags, so a value containing spaces survives intact
instead of being word-split. Export the computed image reference in
post_build so CodeBuild's exported-variables reliably collects it.
An in-place rebuild moves the tag to a new digest and leaves the previous
manifest behind untagged. Delete only removes the current tag, so a
co-managed ECR repository rebuilt in place could still hold the stale
manifest and fail to be torn down. Carry the prior digest through the
build and, once the new build succeeds, delete the prior manifest when
it is no longer referenced by any tag.
The CodeBuild log group is created in the target account and the service
role is granted logs only there, so a push target in a different account
would fail once the build starts. Reject an account mismatch up front,
alongside the existing region check, and document the same-account and
region requirement on the schema.
The generated Dockerfile and build_args.env were written into the build
context, so a Dockerfile with a broad COPY (e.g. COPY . .) would embed
the build-arg values into the image. Build from an empty context
directory with the Dockerfile passed via -f, keeping both helper files
out of the context.
BatchDeleteImage returns an HTTP success with per-image problems reported
in Failures, so an unchecked call could report a still-present image as
deleted and break repository teardown. Inspect Failures (tolerating an
already-gone image) in both the delete and the rebuild-prune paths.

Drop the service-role-derived account check: it ran only after the
internal role was created, so a cross-account rejection would orphan that
role. The same-account requirement stays documented on the schema and is
enforced in practice by the region check and the account-scoped IAM
policy; a cross-account target fails at build time rather than silently.
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.

1 participant