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: formatters:
rich: rich:
datefmt: "[%X]" datefmt: "[%X]"
format: "%(name)25s %(message)s"
handlers: handlers:
console: console:
class: rich.logging.RichHandler class: rich.logging.RichHandler

View file

@ -40,7 +40,7 @@ class Build(BaseModel):
repo: str repo: str
target_branch: str target_branch: str
pr: Optional[int] pr: Optional[int]
commit: str git_commit: str
image: str image: str
link: str link: str
status: models.BuildStatus status: models.BuildStatus
@ -92,11 +92,11 @@ async def trigger_branch(org: str, repo: str, branch: str):
"""Trigger build for a branch.""" """Trigger build for a branch."""
# TODO async github call # TODO async github call
branch_info = github.get_branch_info(org, repo, branch) 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}", repo=f"{branch_info.org}/{branch_info.repo}",
target_branch=branch_info.name, target_branch=branch_info.name,
pr=None, 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.""" """Trigger build for a pull request."""
# TODO async github call # TODO async github call
pull_info = github.get_pull_info(org, repo, pr) 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}", repo=f"{pull_info.org}/{pull_info.repo}",
target_branch=pull_info.target_branch, target_branch=pull_info.target_branch,
pr=pull_info.number, 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: def max_deployed(self) -> int:
return settings.max_deployed 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: def _wakeup(self) -> None:
self._wakeup_event.set() self._wakeup_event.set()
self._wakeup_event.clear() self._wakeup_event.clear()

View file

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

View file

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

View file

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

View file

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

View file

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