Skip to content

Commit d0ebd10

Browse files
ewdurbinclaude
andauthored
Introduce Environments (#195)
* working environments * clean up migrations * Fix render_field layout bug with fieldset/legend in hidden modals Replace fieldset/legend with div/label in the render_field macro. The fieldset/legend combo caused incorrect layout when rendered inside containers that start as display:none (like modals). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * include enviroment on/off in create-project modal * Fix app cards showing wrong counts in environment context The app_card macro was using application-level totals for config count, status, and deployment timestamp even when rendering inside an environment. Now passes the ApplicationEnvironment to the macro so it uses env-scoped configs, status, and deployment info. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refined * fix * don't scale _everything_ to zero * fixup, format, etc * respect requested environment name when enabling environments * enable reviewing envconsul configs in release builds * remove all ephemeral machinery/UI for now... models only * fix for config-write mismatch --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0984f02 commit d0ebd10

36 files changed

Lines changed: 3946 additions & 802 deletions

cabotage/celery/tasks/build.py

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,6 @@ def build_release_buildkit(release):
353353
"context=context",
354354
]
355355
context_configmap_object = release.release_build_context_configmap
356-
buildctl_command = ["buildctl-daemonless.sh"]
357356
with TemporaryDirectory() as tempdir:
358357
os.makedirs(os.path.join(tempdir, "context"), exist_ok=True)
359358
for file, contents in context_configmap_object.data.items():
@@ -365,13 +364,40 @@ def build_release_buildkit(release):
365364
with open(os.path.join(tempdir, "buildkitd.toml"), "w") as f:
366365
f.write(buildkitd_toml)
367366

367+
buildkit_root = f"/tmp/buildkit-{release.application.id}-{release.application_environment_id or 'base'}" # nosec B108 — deterministic path scoped by app+env ID
368+
os.makedirs(buildkit_root, exist_ok=True)
369+
sock_addr = f"unix://{buildkit_root}/buildkitd.sock"
370+
wrapper = os.path.join(tempdir, "buildctl-daemonless.sh")
371+
with open(wrapper, "w") as f:
372+
f.write(
373+
"#!/bin/sh\n"
374+
"set -eu\n"
375+
f"buildkitd --addr={sock_addr} $BUILDKITD_FLAGS &\n"
376+
"pid=$!\n"
377+
'trap "kill $pid || true; wait $pid || true" EXIT\n'
378+
"try=0; max=10\n"
379+
f"until buildctl --addr={sock_addr} debug workers >/dev/null 2>&1; do\n"
380+
" if [ $try -gt $max ]; then\n"
381+
f' echo >&2 "could not connect to {sock_addr} after $max trials"\n'
382+
" exit 1\n"
383+
" fi\n"
384+
" sleep 0.1\n"
385+
" try=$((try + 1))\n"
386+
"done\n"
387+
f'buildctl --addr={sock_addr} "$@"\n'
388+
)
389+
os.chmod(
390+
wrapper, 0o755
391+
) # nosec B103 — wrapper script must be executable
392+
buildctl_command = [wrapper]
393+
368394
try:
369395
output = run_and_stream(
370396
buildctl_command + buildctl_args,
371397
env={
372398
**os.environ,
373399
"BUILDKITD_FLAGS": (
374-
f"--root=/tmp/buildkit-{release.application.id}"
400+
f"--root={buildkit_root}"
375401
f" --config={tempdir}/buildkitd.toml"
376402
" --oci-worker=true --oci-worker-binary=/usr/bin/buildkit-runc"
377403
),
@@ -476,6 +502,7 @@ def _fetch_commit_sha_for_ref(
476502
except GithubException as e:
477503
if e.status == 404:
478504
return None
505+
raise
479506
except UnknownObjectException:
480507
return None
481508

@@ -492,10 +519,18 @@ def _fetch_commit_sha_for_ref(
492519
def fetch_image_build_cache_volume_claim(core_api_instance, image):
493520
volume_claim_name = (
494521
"build-image-cache-"
495-
f"{image.application.project.organization.slug}-"
496-
f"{image.application.project.slug}-"
497-
f"{image.application.slug}"
522+
f"{image.application.project.organization.k8s_identifier}-"
523+
f"{image.application.project.k8s_identifier}-"
524+
f"{image.application.k8s_identifier}"
498525
)
526+
app_env = image.application_environment
527+
if app_env.k8s_identifier is not None:
528+
volume_claim_name += f"-{app_env.environment.k8s_identifier}"
529+
if len(volume_claim_name) > 63:
530+
import hashlib
531+
532+
suffix = hashlib.sha256(volume_claim_name.encode()).hexdigest()[:8]
533+
volume_claim_name = volume_claim_name[:54] + "-" + suffix
499534
try:
500535
volume_claim = core_api_instance.read_namespaced_persistent_volume_claim(
501536
volume_claim_name, "default"
@@ -923,7 +958,6 @@ def file_path(filename):
923958
if not job_complete:
924959
raise BuildError("Image build failed!")
925960
else:
926-
buildctl_command = ["buildctl-daemonless.sh"]
927961
if image.application.github_repository_is_private:
928962
buildctl_args.append("--secret")
929963
buildctl_args.append(
@@ -945,13 +979,40 @@ def file_path(filename):
945979
with open(os.path.join(tempdir, "buildkitd.toml"), "w") as f:
946980
f.write(buildkitd_toml)
947981

982+
buildkit_root = f"/tmp/buildkit-{image.application.id}-{image.application_environment_id or 'base'}" # nosec B108 — deterministic path scoped by app+env ID
983+
os.makedirs(buildkit_root, exist_ok=True)
984+
sock_addr = f"unix://{buildkit_root}/buildkitd.sock"
985+
wrapper = os.path.join(tempdir, "buildctl-daemonless.sh")
986+
with open(wrapper, "w") as f:
987+
f.write(
988+
"#!/bin/sh\n"
989+
"set -eu\n"
990+
f"buildkitd --addr={sock_addr} $BUILDKITD_FLAGS &\n"
991+
"pid=$!\n"
992+
'trap "kill $pid || true; wait $pid || true" EXIT\n'
993+
"try=0; max=10\n"
994+
f"until buildctl --addr={sock_addr} debug workers >/dev/null 2>&1; do\n"
995+
" if [ $try -gt $max ]; then\n"
996+
f' echo >&2 "could not connect to {sock_addr} after $max trials"\n'
997+
" exit 1\n"
998+
" fi\n"
999+
" sleep 0.1\n"
1000+
" try=$((try + 1))\n"
1001+
"done\n"
1002+
f'buildctl --addr={sock_addr} "$@"\n'
1003+
)
1004+
os.chmod(
1005+
wrapper, 0o755
1006+
) # nosec B103 — wrapper script must be executable
1007+
buildctl_command = [wrapper]
1008+
9481009
try:
9491010
output = run_and_stream(
9501011
buildctl_command + buildctl_args,
9511012
env={
9521013
**os.environ,
9531014
"BUILDKITD_FLAGS": (
954-
f"--root=/tmp/buildkit-{image.application.id}"
1015+
f"--root={buildkit_root}"
9551016
f" --config={tempdir}/buildkitd.toml"
9561017
" --oci-worker=true --oci-worker-binary=/usr/bin/buildkit-runc"
9571018
),
@@ -1037,7 +1098,8 @@ def run_image_build(image_id=None, buildkit=False):
10371098
try:
10381099
build_metadata = build_image_buildkit(image)
10391100
if (
1040-
"installation_id" in image.image_metadata
1101+
image.image_metadata
1102+
and "installation_id" in image.image_metadata
10411103
and "statuses_url" in image.image_metadata
10421104
):
10431105
access_token = github_app.fetch_installation_access_token(
@@ -1055,7 +1117,8 @@ def run_image_build(image_id=None, buildkit=False):
10551117
image.error_detail = str(exc)
10561118
db.session.commit()
10571119
if (
1058-
"installation_id" in image.image_metadata
1120+
image.image_metadata
1121+
and "installation_id" in image.image_metadata
10591122
and "statuses_url" in image.image_metadata
10601123
):
10611124
access_token = github_app.fetch_installation_access_token(
@@ -1103,7 +1166,8 @@ def run_image_build(image_id=None, buildkit=False):
11031166
and image.image_metadata
11041167
and image.image_metadata.get("auto_deploy", False)
11051168
):
1106-
release = image.application.create_release()
1169+
app_env = image.application_environment
1170+
release = image.application.create_release(app_env=app_env)
11071171
release.release_metadata = image.image_metadata
11081172
db.session.add(release)
11091173
db.session.flush()
@@ -1222,6 +1286,7 @@ def run_release_build(release_id=None):
12221286
):
12231287
deployment = Deployment(
12241288
application_id=release.application.id,
1289+
application_environment_id=release.application_environment_id,
12251290
release=release.asdict,
12261291
deploy_metadata=release.release_metadata,
12271292
)

0 commit comments

Comments
 (0)