- commit/rollback transactions
- identify supported branch prefixes
- query builds api
- better exceptions
This commit is contained in:
Stéphane Bidoul 2021-10-17 17:24:56 +02:00
parent c2c2e4bac9
commit 488d620f40
No known key found for this signature in database
GPG key ID: BCAB2555446B5B92
6 changed files with 73 additions and 26 deletions

View file

@ -1,11 +1,11 @@
import datetime import datetime
from enum import Enum
from typing import List from typing import List
from fastapi import Depends from fastapi import Depends, HTTPException
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from starlette.status import HTTP_404_NOT_FOUND
from . import github, models from . import github, models
from .app import app from .app import app
@ -45,14 +45,10 @@ class Branch(BaseModel):
response_model=List[Branch], response_model=List[Branch],
) )
def branches(repo_id: str, db: Session = Depends(get_db)): def branches(repo_id: str, db: Session = Depends(get_db)):
return db.query(models.Branch).filter(models.Branch.repo_id == repo_id).all() repo = db.query(models.Repo).get(repo_id)
if not repo:
raise HTTPException(HTTP_404_NOT_FOUND)
class BuildStatus(str, Enum): return db.query(models.Branch).filter(models.Branch.repo == repo).all()
stopped = "stopped"
running = "running"
deploying = "deploying"
not_deployed = "not_deployed"
class Build(BaseModel): class Build(BaseModel):
@ -60,7 +56,7 @@ class Build(BaseModel):
created: datetime.datetime created: datetime.datetime
display_name: str display_name: str
display_url: str = Field(title="Link to open the build") display_url: str = Field(title="Link to open the build")
status: BuildStatus status: models.BuildStatus
class Config: class Config:
orm_mode = True orm_mode = True
@ -70,8 +66,18 @@ class Build(BaseModel):
"/repos/{repo_id}/branches/{branch_id}/builds", "/repos/{repo_id}/branches/{branch_id}/builds",
response_model=List[Build], response_model=List[Build],
) )
def builds(repo_id: str, branch_id: str): def builds(repo_id: str, branch_id: str, db: Session = Depends(get_db)):
... repo = db.query(models.Repo).get(repo_id)
if not repo:
raise HTTPException(HTTP_404_NOT_FOUND)
branch = (
db.query(models.Branch)
.filter(models.Branch.id == branch_id, models.Branch.repo == repo)
.one_or_none()
)
if not branch:
raise HTTPException(HTTP_404_NOT_FOUND)
return db.query(models.Build).filter(models.Build.branch == branch).all()
@app.get( @app.get(

View file

@ -1,4 +1,6 @@
from typing import Optional import re
from .exceptions import BranchNotSupported
images = { images = {
"15.0": "ghcr.io/oca/oca-ci/py3.8-odoo15.0:latest", "15.0": "ghcr.io/oca/oca-ci/py3.8-odoo15.0:latest",
@ -10,5 +12,27 @@ images = {
} }
def get_build_image(target_branch: str) -> Optional[str]: TARGET_BRANCH_RE = re.compile(r"^(\d+\.\d+)")
return images.get(target_branch)
def get_target_branch(branch_name: str) -> str:
mo = TARGET_BRANCH_RE.match(branch_name)
if not mo:
raise BranchNotSupported(
f"Malformed branch name {branch_name} "
f"(it should start with an Odoo branch name)."
)
if mo:
key = mo.group(1)
if key not in images:
raise BranchNotSupported(
f"No build image configured for {key} (from {branch_name})."
)
return key
check_branch_supported = get_target_branch
def get_build_image(branch_name: str) -> str:
return images[get_target_branch(branch_name)]

View file

@ -1,8 +1,6 @@
from typing import Generator
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session, sessionmaker from sqlalchemy.orm import sessionmaker
from .settings import settings from .settings import settings
@ -15,9 +13,14 @@ def create_tables() -> None:
Base.metadata.create_all(engine) Base.metadata.create_all(engine)
def get_db() -> Generator[Session]: def get_db():
db = SessionLocal() db = SessionLocal()
try: try:
yield db yield db
except Exception:
db.rollback()
raise
else:
db.commit()
finally: finally:
db.close() db.close()

View file

@ -12,3 +12,7 @@ class BranchNotFound(ClientError):
class NotFoundOnGithub(ClientError): class NotFoundOnGithub(ClientError):
pass pass
class BranchNotSupported(ClientError):
pass

View file

@ -13,7 +13,7 @@ def _github_get(url: str) -> Any:
} }
response = requests.get(full_url, headers) response = requests.get(full_url, headers)
if response.status_code == 404: if response.status_code == 404:
raise NotFoundOnGithub(full_url) raise NotFoundOnGithub(f"GitHub URL not found: {full_url}.")
response.raise_for_status() response.raise_for_status()
return response.json() return response.json()

View file

@ -1,3 +1,4 @@
from enum import Enum
from typing import List from typing import List
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, func from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, func
@ -5,7 +6,7 @@ from sqlalchemy.ext.compiler import compiles
from sqlalchemy.orm import Session, relationship from sqlalchemy.orm import Session, relationship
from sqlalchemy.sql import expression from sqlalchemy.sql import expression
from .build_images import get_build_image from .build_images import check_branch_supported, get_build_image
from .db import Base from .db import Base
from .exceptions import RepoNotFound from .exceptions import RepoNotFound
from .github import BranchInfo, PullRequestInfo from .github import BranchInfo, PullRequestInfo
@ -55,7 +56,7 @@ class Repo(Base):
.one_or_none() .one_or_none()
) )
if repo is None: if repo is None:
raise RepoNotFound() raise RepoNotFound(f"Repo {org}/{name} not supported by this runboat.")
return repo return repo
@ -106,6 +107,7 @@ class Branch(Base):
.one_or_none() .one_or_none()
) )
if branch is None: if branch is None:
check_branch_supported(branch_info.name)
branch = Branch( branch = Branch(
repo=repo, repo=repo,
target_branch=branch_info.name, target_branch=branch_info.name,
@ -128,6 +130,7 @@ class Branch(Base):
.one_or_none() .one_or_none()
) )
if branch is None: if branch is None:
check_branch_supported(pr_info.target_branch)
branch = Branch( branch = Branch(
repo=repo, repo=repo,
target_branch=pr_info.target_branch, target_branch=pr_info.target_branch,
@ -138,6 +141,13 @@ class Branch(Base):
return branch return branch
class BuildStatus(str, Enum):
stopped = "stopped"
running = "running"
deploying = "deploying"
not_deployed = "not_deployed"
class Build(Base): class Build(Base):
__tablename__ = "build" __tablename__ = "build"
@ -147,9 +157,9 @@ class Build(Base):
branch_id = Column(Integer, ForeignKey("branch.id"), nullable=False, index=True) branch_id = Column(Integer, ForeignKey("branch.id"), nullable=False, index=True)
branch: Branch = relationship(Branch, back_populates="builds") branch: Branch = relationship(Branch, back_populates="builds")
build_image = Column(String, nullable=False) build_image: str = Column(String, nullable=False)
git_sha: str = Column(String, nullable=False) git_sha: str = Column(String, nullable=False)
status: str = Column(String, nullable=False) status: BuildStatus = Column(String, nullable=False)
# ressource_label = Column(String, nullable=False, unique=True, index=True) # ressource_label = Column(String, nullable=False, unique=True, index=True)
# TODO: add unique constraint on branch_id + git_sha # TODO: add unique constraint on branch_id + git_sha
@ -182,7 +192,7 @@ class Build(Base):
branch=branch, branch=branch,
git_sha=git_sha, git_sha=git_sha,
build_image=build_image, build_image=build_image,
status="not_deployed", # TODO use same enum as in API status=BuildStatus.not_deployed,
) )
db.add(build) db.add(build)
db.flush() db.flush()