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:
parent
1ca5295d54
commit
e2eb51f657
6 changed files with 57 additions and 19 deletions
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
26
app/main.py
26
app/main.py
|
|
@ -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}")
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue