ci(release): build image for all branches, not just feature/** (#19)

* dryrun: intentional failing test (will be reverted)

* feat(auth): optional SEED_ADMIN_PASSWORD to pre-hash seed admin (dev helper)

Terraform gains enable_seed_password + seed_admin_password (sensitive) vars
on the customer-instance module; when enabled the password is piped via
startup-script into /opt/agnes/.env as SEED_ADMIN_PASSWORD. On first boot
app/main.py argon2-hashes it onto the seed user so the admin can log in
immediately without going through /auth/bootstrap. Never overwrites an
existing password_hash — safe against accidental reset on terraform apply.

* ci(release): build :dev-<slug> on any branch, not just feature/**

Before: only 'feature/**' branches triggered release.yml, so pushing
'zs/my-edit' or 'fix/bug' did not publish an image. dev_instances entry
pinning image_tag = 'dev-zs-my-edit' then crashed VM startup with
'image not found'.

Now: any branch push (except main, which produces :stable) publishes
:dev-<slug>. Slug strips a leading 'feature/' and replaces non-[a-z0-9-]
with '-', keeping existing feature/** behavior identical.

* Revert "dryrun: intentional failing test (will be reverted)"

This reverts commit cf9cc06a7884bb401ff29fc5cb6d8baf84dc3daa.
This commit is contained in:
ZdenekSrotyr 2026-04-21 21:33:57 +02:00 committed by GitHub
parent 1ca5295d54
commit e2eb51f657
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 57 additions and 19 deletions

View file

@ -2,7 +2,9 @@ name: Release
on: on:
push: push:
branches: [main, "feature/**"] branches:
- main
- "**" # build :dev-<slug> image for any branch push (e.g. feature/x, zs/edit, fix/y)
permissions: permissions:
contents: write contents: write

View file

@ -90,7 +90,10 @@ def create_app() -> FastAPI:
SCHEMA_VERSION, SCHEMA_VERSION,
) )
# Seed admin user for testing/CI (when SEED_ADMIN_EMAIL is set) # Seed admin user for testing/CI (when SEED_ADMIN_EMAIL is set).
# Optional: SEED_ADMIN_PASSWORD sets password_hash on first seed so the user
# can log in immediately without bootstrap. Only applied if the user has no
# password_hash yet — never overwrites an existing password.
seed_email = os.environ.get("SEED_ADMIN_EMAIL") seed_email = os.environ.get("SEED_ADMIN_EMAIL")
if seed_email: if seed_email:
try: try:
@ -98,10 +101,25 @@ def create_app() -> FastAPI:
from src.repositories.users import UserRepository from src.repositories.users import UserRepository
conn = get_system_db() conn = get_system_db()
repo = UserRepository(conn) repo = UserRepository(conn)
if not repo.get_by_email(seed_email): seed_password = os.environ.get("SEED_ADMIN_PASSWORD") or None
password_hash = None
if seed_password:
from argon2 import PasswordHasher
password_hash = PasswordHasher().hash(seed_password)
existing = repo.get_by_email(seed_email)
if not existing:
import uuid import uuid
repo.create(id=str(uuid.uuid4()), email=seed_email, name="Admin", role="admin") repo.create(
logger.info("Seeded admin user: %s", seed_email) id=str(uuid.uuid4()),
email=seed_email,
name="Admin",
role="admin",
password_hash=password_hash,
)
logger.info("Seeded admin user: %s (password=%s)", seed_email, "yes" if password_hash else "no")
elif password_hash and not existing.get("password_hash"):
repo.update(id=existing["id"], password_hash=password_hash, role="admin")
logger.info("Set password on existing seed admin: %s", seed_email)
conn.close() conn.close()
except Exception as e: except Exception as e:
logger.warning(f"Could not seed admin: {e}") logger.warning(f"Could not seed admin: {e}")

View file

@ -21,6 +21,8 @@ SESSION_SECRET= # python -c "import secrets; print(secrets.token_he
# ── BOOTSTRAP (first deploy only) ─────────────────── # ── BOOTSTRAP (first deploy only) ───────────────────
# SEED_ADMIN_EMAIL=admin@example.com # SEED_ADMIN_EMAIL=admin@example.com
# SEED_ADMIN_PASSWORD= # Dev helper only — sets password_hash on seed.
# # Never overwrites an existing password.
# ── EMAIL / SMTP (required for magic link auth) ───── # ── EMAIL / SMTP (required for magic link auth) ─────
# SMTP_HOST=smtp.gmail.com # SMTP_HOST=smtp.gmail.com

View file

@ -219,6 +219,7 @@ resource "google_compute_instance" "vm" {
data_source = var.data_source data_source = var.data_source
keboola_stack_url = var.keboola_stack_url keboola_stack_url = var.keboola_stack_url
seed_admin_email = var.seed_admin_email seed_admin_email = var.seed_admin_email
seed_admin_password = var.enable_seed_password ? var.seed_admin_password : ""
role = each.value.role role = each.value.role
compose_ref = var.compose_ref compose_ref = var.compose_ref
}) })

View file

@ -15,6 +15,7 @@ ACME_EMAIL="${acme_email}"
DATA_SOURCE="${data_source}" DATA_SOURCE="${data_source}"
KEBOOLA_STACK_URL="${keboola_stack_url}" KEBOOLA_STACK_URL="${keboola_stack_url}"
SEED_ADMIN_EMAIL="${seed_admin_email}" SEED_ADMIN_EMAIL="${seed_admin_email}"
SEED_ADMIN_PASSWORD="${seed_admin_password}"
ROLE="${role}" ROLE="${role}"
COMPOSE_REF="${compose_ref}" COMPOSE_REF="${compose_ref}"
@ -81,6 +82,7 @@ DATA_SOURCE=$DATA_SOURCE
KEBOOLA_STORAGE_TOKEN=$KEBOOLA_TOKEN KEBOOLA_STORAGE_TOKEN=$KEBOOLA_TOKEN
KEBOOLA_STACK_URL=$KEBOOLA_STACK_URL KEBOOLA_STACK_URL=$KEBOOLA_STACK_URL
SEED_ADMIN_EMAIL=$SEED_ADMIN_EMAIL SEED_ADMIN_EMAIL=$SEED_ADMIN_EMAIL
SEED_ADMIN_PASSWORD=$SEED_ADMIN_PASSWORD
LOG_LEVEL=info LOG_LEVEL=info
DOMAIN=$DOMAIN DOMAIN=$DOMAIN
AGNES_TAG=$IMAGE_TAG AGNES_TAG=$IMAGE_TAG

View file

@ -53,6 +53,19 @@ variable "seed_admin_email" {
type = string type = string
} }
variable "enable_seed_password" {
description = "Pokud true, seed admin user dostane hned password_hash ze seed_admin_password (dev helper). Ponech false v prod — admin si heslo nastaví přes /auth/bootstrap nebo Google OAuth."
type = bool
default = false
}
variable "seed_admin_password" {
description = "Plain-text heslo pro seed admina. Použije se jen když enable_seed_password=true. POZOR: ukládá se do Terraform state."
type = string
default = ""
sensitive = true
}
variable "data_source" { variable "data_source" {
description = "Typ data source — keboola | bigquery | csv" description = "Typ data source — keboola | bigquery | csv"
type = string type = string