Do not create new deployment if build already exist

This commit is contained in:
Stéphane Bidoul 2021-10-29 00:32:15 +02:00
parent c2b2d88f4c
commit 1f137bbdc8
No known key found for this signature in database
GPG key ID: BCAB2555446B5B92
8 changed files with 71 additions and 27 deletions

View file

@ -3,6 +3,7 @@ disable_existing_loggers: false
formatters:
rich:
datefmt: "[%X]"
format: "%(name)25s %(message)s"
handlers:
console:
class: rich.logging.RichHandler

View file

@ -40,7 +40,7 @@ class Build(BaseModel):
repo: str
target_branch: str
pr: Optional[int]
commit: str
git_commit: str
image: str
link: str
status: models.BuildStatus
@ -92,11 +92,11 @@ async def trigger_branch(org: str, repo: str, branch: str):
"""Trigger build for a branch."""
# TODO async github call
branch_info = github.get_branch_info(org, repo, branch)
await models.Build.deploy(
await controller.deploy_or_delay_start(
repo=f"{branch_info.org}/{branch_info.repo}",
target_branch=branch_info.name,
pr=None,
commit=branch_info.head_sha,
git_commit=branch_info.head_sha,
)
@ -109,11 +109,11 @@ async def trigger_pull(org: str, repo: str, pr: int):
"""Trigger build for a pull request."""
# TODO async github call
pull_info = github.get_pull_info(org, repo, pr)
await models.Build.deploy(
await controller.deploy_or_delay_start(
repo=f"{pull_info.org}/{pull_info.repo}",
target_branch=pull_info.target_branch,
pr=pull_info.number,
commit=pull_info.head_sha,
git_commit=pull_info.head_sha,
)

View file

@ -60,6 +60,25 @@ class Controller:
def max_deployed(self) -> int:
return settings.max_deployed
async def deploy_or_delay_start(
self, repo: str, target_branch: str, pr: int | None, git_commit: str
) -> None:
build = self.db.get_for_commit(
repo=repo,
target_branch=target_branch,
pr=pr,
git_commit=git_commit,
)
if build is not None:
await build.delay_start()
return
await Build.deploy(
repo=repo,
target_branch=target_branch,
pr=pr,
git_commit=git_commit,
)
def _wakeup(self) -> None:
self._wakeup_event.set()
self._wakeup_event.clear()

View file

@ -33,7 +33,7 @@ class BuildsDb:
" repo TEXT NOT NULL, "
" target_branch TEXT NOT NULL, "
" pr INTEGER, "
" 'commit' TEXT NOT NULL, "
" git_commit TEXT NOT NULL, "
" image TEXT NOT NULL,"
" status TEXT NOT NULL, "
" todo TEXT, "
@ -51,6 +51,21 @@ class BuildsDb:
return None
return self._build_from_row(row)
def get_for_commit(
self, repo: str, target_branch: str, pr: int | None, git_commit: str
) -> Build | None:
query = "SELECT * FROM builds WHERE repo=? AND target_branch=? AND git_commit=?"
params = [repo.lower(), target_branch, git_commit]
if pr:
query += " AND pr=?"
params.append(pr)
else:
query += " AND pr IS NULL"
row = self._con.execute(query, params).fetchone()
if not row:
return None
return self._build_from_row(row)
def remove(self, name: str) -> None:
with self._con:
self._con.execute("DELETE FROM builds WHERE name=?", (name,))
@ -65,7 +80,7 @@ class BuildsDb:
" repo,"
" target_branch,"
" pr,"
" 'commit',"
" git_commit,"
" image,"
" status,"
" todo, "
@ -79,7 +94,7 @@ class BuildsDb:
build.repo,
build.target_branch,
build.pr,
build.commit,
build.git_commit,
build.image,
build.status,
build.todo,
@ -99,6 +114,7 @@ class BuildsDb:
def to_start(self, limit: int) -> list[Build]:
"""Return the list of builds to start, ordered by todo timestamp."""
# TODO ordering is not correct as setting todo does not set last_scaled
rows = self._con.execute(
"SELECT * FROM builds WHERE todo=? ORDER BY last_scaled LIMIT ?",
(BuildTodo.start, limit),
@ -127,7 +143,7 @@ class BuildsDb:
for row in self._con.execute(
"SELECT * FROM builds WHERE repo=?"
"ORDER BY target_branch, pr, created DESC",
(repo,),
(repo.lower(),),
).fetchall():
build = self._build_from_row(row)
if (

View file

@ -53,7 +53,7 @@ class DeploymentVars(BaseModel):
repo: str
target_branch: str
pr: Optional[int]
commit: str
git_commit: str
image_name: str
image_tag: str
pghost: str
@ -71,7 +71,7 @@ def make_deployment_vars(
repo: str,
target_branch: str,
pr: int | None,
commit: str,
git_commit: str,
image: str,
) -> DeploymentVars:
image_name, image_tag = _split_image_name_tag(image)
@ -81,7 +81,7 @@ def make_deployment_vars(
repo=repo,
target_branch=target_branch,
pr=pr,
commit=commit,
git_commit=git_commit,
image_name=image_name,
image_tag=image_tag,
pghost=settings.build_pghost,
@ -118,6 +118,14 @@ async def _kubectl(args: list[str]) -> None:
async def deploy(deployment_vars: DeploymentVars) -> None:
with _render_kubefiles(deployment_vars) as tmp_path:
await _kubectl(
[
"apply",
"--dry-run=server",
"-k",
str(tmp_path),
]
)
await _kubectl(
[
"apply",

View file

@ -14,7 +14,7 @@ commonAnnotations:
runboat/repo: "{{ repo }}"
runboat/target-branch: "{{ target_branch }}"
runboat/pr: "{{ pr if pr else '' }}"
runboat/commit: "{{ commit }}"
runboat/git-commit: "{{ git_commit }}"
images:
- name: odoo
@ -37,7 +37,7 @@ configMapGenerator:
- PGDATABASE={{ pgdatabase }}
- ADDONS_DIR=/build
- RUNBOAT_GIT_REPO=https://github.com/{{ repo }}
- RUNBOAT_GIT_REF={{ commit }}
- RUNBOAT_GIT_REF={{ git_commit }}
- name: runboat-scripts
files:
- runboat-clone-and-install.sh

View file

@ -31,7 +31,7 @@ class Build(BaseModel):
repo: str
target_branch: str
pr: Optional[int]
commit: str
git_commit: str
image: str
status: BuildStatus
todo: Optional[BuildTodo]
@ -46,7 +46,7 @@ class Build(BaseModel):
repo=deployment.metadata.annotations["runboat/repo"],
target_branch=deployment.metadata.annotations["runboat/target-branch"],
pr=deployment.metadata.annotations["runboat/pr"] or None,
commit=deployment.metadata.annotations["runboat/commit"],
git_commit=deployment.metadata.annotations["runboat/git-commit"],
image=deployment.spec.template.spec.containers[0].image,
status=cls._status_from_deployment(deployment),
todo=deployment.metadata.annotations["runboat/todo"] or None,
@ -71,17 +71,17 @@ class Build(BaseModel):
@classmethod
def make_slug(
cls, repo: str, target_branch: str, pr: int | None, commit: str
cls, repo: str, target_branch: str, pr: int | None, git_commit: str
) -> str:
slug = f"{slugify(repo)}-{slugify(target_branch)}"
if pr:
slug = f"{slug}-pr{slugify(pr)}"
slug = f"{slug}-{commit[:12]}"
slug = f"{slug}-{git_commit[:12]}"
return slug
@property
def slug(self) -> str:
return self.make_slug(self.repo, self.target_branch, self.pr, self.commit)
return self.make_slug(self.repo, self.target_branch, self.pr, self.git_commit)
@property
def link(self) -> str:
@ -147,11 +147,11 @@ class Build(BaseModel):
@classmethod
async def deploy(
cls, repo: str, target_branch: str, pr: int | None, commit: str
cls, repo: str, target_branch: str, pr: int | None, git_commit: str
) -> None:
"""Deploy a build, without starting it."""
name = str(uuid.uuid4())
slug = cls.make_slug(repo, target_branch, pr, commit)
slug = cls.make_slug(repo, target_branch, pr, git_commit)
_logger.info(f"Deploying {slug} ({name})")
image = get_build_image(target_branch)
deployment_vars = k8s.make_deployment_vars(
@ -160,7 +160,7 @@ class Build(BaseModel):
repo.lower(),
target_branch,
pr,
commit,
git_commit,
image,
)
await k8s.deploy(deployment_vars)

View file

@ -2,7 +2,7 @@ import logging
from fastapi import APIRouter, BackgroundTasks, Header, Request
from . import models
from .controller import controller
from .settings import settings
_logger = logging.getLogger(__name__)
@ -29,17 +29,17 @@ async def receive_payload(
if x_github_event == "pull_request":
if action in ("opened", "synchronize"):
background_tasks.add_task(
models.Build.deploy,
controller.deploy_or_delay_start,
repo=repo,
target_branch=payload["pull_request"]["base"]["ref"],
pr=payload["pull_request"]["number"],
commit=payload["pull_request"]["head"]["sha"],
git_commit=payload["pull_request"]["head"]["sha"],
)
elif x_github_event == "push":
background_tasks.add_task(
models.Build.deploy,
controller.deploy_or_delay_start,
repo=repo,
target_branch=payload["ref"].split("/")[-1],
pr=None,
commit=payload["after"],
git_commit=payload["after"],
)