strip docker type — site-publish is static-content only

Removes type: docker handling from action.yaml, scripts (build/deploy/utils/setup),
and templates (deployment.yaml.j2, service-docker.yaml.j2). Renamed
service-static.yaml.j2 -> service.yaml.j2.

If site.yaml has type: docker, parse_site_yaml() now dies with a clear message
pointing to action/image-build + action/image-push + action/image-deploy with
hand-authored apps-repo manifests. rainsounds.vino.network was the only docker
consumer and has already migrated.

Drops registry-password input from action.yaml (no longer needed).
This commit is contained in:
Donavan Fritz
2026-05-06 10:01:09 -05:00
parent e53776af5e
commit 8cc34552c6
13 changed files with 69 additions and 250 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3 -36
View File
@@ -1,12 +1,11 @@
"""Build phase — content prep (static) or docker image build."""
"""Build phase — content prep for static-content sites."""
import os
import shutil
import subprocess
import tempfile
from pathlib import Path
from utils import EXCLUDE_FILES, die, env, parse_site_yaml, run
from utils import EXCLUDE_FILES, env, parse_site_yaml, run
def build_static(site_dir, cfg):
@@ -55,35 +54,6 @@ def build_static(site_dir, cfg):
print(f"Build complete — content at {html_dir}")
def docker_login():
password = os.environ.get("REGISTRY_PASSWORD", "")
user = env("CI_BOT_USER", "ci-bot")
if not password:
die("REGISTRY_PASSWORD is required for docker builds")
subprocess.run(
f"echo '{password}' | docker login code.fritzlab.net -u {user} --password-stdin",
shell=True, check=True,
)
def build_docker(site_dir, cfg):
docker_login()
image = cfg["image"]
run_number = env("GITHUB_RUN_NUMBER", "0")
tags = [f"{image}:latest", f"{image}:{run_number}"]
cmd_parts = ["docker", "build"]
for tag in tags:
cmd_parts += ["-t", tag]
cmd_parts += ["--network", "host", "--provenance=false"]
for key, val in cfg.get("build_args", {}).items():
cmd_parts += ["--build-arg", f"{key}={val}"]
cmd_parts.append(str(site_dir))
run(" ".join(cmd_parts))
print(f"Docker build complete — tags: {', '.join(tags)}")
def cmd_build():
site_dir = Path(env("SITE_DIR"))
cfg = parse_site_yaml(site_dir)
@@ -92,7 +62,4 @@ def cmd_build():
print("Site disabled — skipping build")
return
if cfg["type"] == "docker":
build_docker(site_dir, cfg)
else:
build_static(site_dir, cfg)
build_static(site_dir, cfg)
+4 -90
View File
@@ -1,9 +1,8 @@
"""Deploy phase — S3 sync or docker push, manifest rendering, alias reconcile."""
"""Deploy phase — S3 sync, manifest rendering, alias reconcile."""
import json
import os
import shutil
import subprocess
import tempfile
from pathlib import Path
from urllib.error import HTTPError, URLError
@@ -108,62 +107,7 @@ def ensure_bucket_aliases(site_name, aliases, admin_token):
raise
def docker_push(cfg):
image = cfg["image"]
run_number = env("GITHUB_RUN_NUMBER", "0")
for tag in [f"{image}:latest", f"{image}:{run_number}"]:
run(f"docker push {tag}")
def docker_tag_cleanup(cfg, token):
"""Keep the 3 newest numeric tags, delete the rest via Gitea API."""
image = cfg["image"]
parts = image.split("/")
if len(parts) < 3:
print(f"Cannot parse image for tag cleanup: {image}")
return
org = parts[1]
pkg_name = parts[2]
url = f"https://{GITEA_HOST}/api/v1/packages/{org}?type=container"
req = Request(url, headers={"Authorization": f"token {token}"})
try:
with urlopen(req) as resp:
packages = json.loads(resp.read())
except Exception as e:
print(f"Warning: failed to list packages for cleanup: {e}")
return
numeric_versions = []
for pkg in packages:
if pkg.get("name") == pkg_name:
ver = pkg.get("version", "")
if ver.isdigit():
numeric_versions.append(int(ver))
numeric_versions.sort()
to_delete = numeric_versions[:-3] if len(numeric_versions) > 3 else []
for ver in to_delete:
del_url = f"https://{GITEA_HOST}/api/v1/packages/{org}/container/{pkg_name}/{ver}"
print(f" Deleting {pkg_name}:{ver}")
req = Request(del_url, method="DELETE", headers={"Authorization": f"token {token}"})
try:
urlopen(req)
except Exception as e:
print(f" Warning: failed to delete {pkg_name}:{ver}: {e}")
if to_delete:
print(f" Cleaned up {len(to_delete)} old tags")
else:
print(" No old tags to clean up")
def docker_cleanup():
run("docker system prune --all --force")
def render_site_manifests(site_name, action_dir, app_dir, manifests_dir, cfg, extra=None):
def render_site_manifests(site_name, action_dir, app_dir, manifests_dir, cfg):
"""Always re-render manifests from current site.yaml. Templates own
domain + aliases, so changes propagate without manual edits."""
manifests_dir.mkdir(parents=True, exist_ok=True)
@@ -173,11 +117,8 @@ def render_site_manifests(site_name, action_dir, app_dir, manifests_dir, cfg, ex
"domain": cfg["domain"],
"aliases": cfg["aliases"],
"namespace": NAMESPACE,
"site_type": cfg["type"],
}
if extra:
template_vars.update(extra)
render_templates(action_dir, template_vars, app_dir, manifests_dir, cfg["type"])
render_templates(action_dir, template_vars, app_dir, manifests_dir)
def deploy_static(site_name, site_dir, action_dir, token, cfg):
@@ -193,30 +134,6 @@ def deploy_static(site_name, site_dir, action_dir, token, cfg):
commit_and_push(apps_dir, f"Deploy {site_name}")
def deploy_docker(site_name, site_dir, action_dir, token, cfg):
docker_push(cfg)
run_number = env("GITHUB_RUN_NUMBER", "0")
image = cfg["image"]
apps_dir = clone_apps(token)
app_dir = apps_dir / "sjc001" / "websites" / site_name
manifests_dir = app_dir / "manifests"
render_site_manifests(site_name, action_dir, app_dir, manifests_dir, cfg, extra={
"image": image,
"port": cfg["port"],
"health_path": cfg["health_path"],
"replicas": cfg["replicas"],
})
# Pin to this build's tag (kustomize edit appends an `images:` override).
run(f"cd {manifests_dir} && kustomize edit set image {image}={image}:{run_number}")
commit_and_push(apps_dir, f"Deploy {site_name} #{run_number}")
docker_tag_cleanup(cfg, token)
docker_cleanup()
def decommission(site_name, token):
"""Remove manifests from apps repo."""
user = env("CI_BOT_USER", "ci-bot")
@@ -249,7 +166,4 @@ def cmd_deploy():
decommission(site_name, token)
return
if cfg["type"] == "docker":
deploy_docker(site_name, site_dir, action_dir, token, cfg)
else:
deploy_static(site_name, site_dir, action_dir, token, cfg)
deploy_static(site_name, site_dir, action_dir, token, cfg)
-8
View File
@@ -24,13 +24,6 @@ def ensure_aws():
subprocess.run(["aws", "--version"], check=True)
def ensure_docker():
if shutil.which("docker"):
subprocess.run(["docker", "version", "--format", "{{.Client.Version}}"], check=True)
return
print("WARNING: docker CLI not found — docker builds will fail", file=sys.stderr)
def ensure_jinja2():
try:
import jinja2
@@ -45,5 +38,4 @@ def ensure_jinja2():
if __name__ == "__main__":
ensure_jinja2()
ensure_aws()
ensure_docker()
print("Setup complete")
+31 -27
View File
@@ -1,4 +1,4 @@
"""Shared utilities for the publish-site action."""
"""Shared utilities for the site-publish action."""
import os
import shutil
@@ -20,7 +20,26 @@ EXCLUDE_FILES = {
"Dockerfile", ".dockerignore", "go.mod", "go.sum",
}
VALID_TYPES = {"static", "hugo", "mkdocs", "docker"}
VALID_TYPES = {"static", "hugo", "mkdocs"}
DOCKER_DEPRECATION_MSG = """\
type: docker is no longer supported by action/site-publish.
site-publish handles only static-content sites (static, hugo, mkdocs)
that ship to Garage S3. For containerized web apps, use the standard
image-producer chain:
- uses: action/image-build@v1 # build + smoke-test
- uses: action/image-push@v1 # push + prune
- uses: action/image-deploy@v1 # apps repo image-pin
Hand-author your apps-repo manifests once (Deployment, Service, Ingress,
Certificate, kustomization with images: block) under
sjc001/websites/<repo>/manifests/. image-deploy will pin the tag on
every CI run. See action/image-deploy README and
sjc001/websites/rainsounds.vino.network/manifests/ for the canonical
example.\
"""
def k8s_name(name):
@@ -57,6 +76,10 @@ def parse_site_yaml(site_dir):
die("domain is required in site.yaml")
site_type = cfg.get("type", "static")
if site_type == "docker":
die(DOCKER_DEPRECATION_MSG)
if site_type not in VALID_TYPES:
die(f"Unknown site type: {site_type} (valid: {', '.join(sorted(VALID_TYPES))})")
@@ -65,20 +88,10 @@ def parse_site_yaml(site_dir):
"type": site_type,
"enabled": cfg.get("enabled", True),
"aliases": cfg.get("aliases") or [],
"content_dir": cfg.get("content_dir", ""),
"tidy": cfg.get("tidy", True),
}
if site_type == "docker":
if not cfg.get("image"):
die("image is required in site.yaml for type: docker")
site["image"] = cfg["image"]
site["port"] = cfg.get("port", 8080)
site["build_args"] = cfg.get("build_args") or {}
site["health_path"] = cfg.get("health_path", "/healthz")
site["replicas"] = cfg.get("replicas", 1)
else:
site["content_dir"] = cfg.get("content_dir", "")
site["tidy"] = cfg.get("tidy", True)
print("Site config:")
for k, v in site.items():
print(f" {k}: {v}")
@@ -96,30 +109,21 @@ def clone_apps(token):
return apps_dir
def render_templates(action_dir, template_vars, app_dir, manifests_dir, site_type):
"""Render Jinja2 templates, selecting the right set for the site type."""
def render_templates(action_dir, template_vars, app_dir, manifests_dir):
"""Render Jinja2 templates for a static-content site."""
templates_dir = Path(action_dir) / "templates"
jinja_env = Environment(
loader=FileSystemLoader(str(templates_dir)),
keep_trailing_newline=True,
)
if site_type == "docker":
service_tmpl = "service-docker.yaml.j2"
tmpl_names = ["app.yaml.j2", "certificate.yaml.j2", "ingress.yaml.j2",
"kustomization.yaml.j2", service_tmpl, "deployment.yaml.j2"]
else:
service_tmpl = "service-static.yaml.j2"
tmpl_names = ["app.yaml.j2", "certificate.yaml.j2", "ingress.yaml.j2",
"kustomization.yaml.j2", service_tmpl]
tmpl_names = ["app.yaml.j2", "certificate.yaml.j2", "ingress.yaml.j2",
"kustomization.yaml.j2", "service.yaml.j2"]
for tmpl_name in tmpl_names:
tmpl = jinja_env.get_template(tmpl_name)
rendered = tmpl.render(**template_vars)
out_name = tmpl_name.replace(".j2", "")
# service-docker.yaml / service-static.yaml → service.yaml
if out_name.startswith("service-"):
out_name = "service.yaml"
dest = app_dir / out_name if tmpl_name == "app.yaml.j2" else manifests_dir / out_name
dest.write_text(rendered)
print(f" Rendered {tmpl_name} -> {dest}")