WIP
- commit/rollback transactions - identify supported branch prefixes - query builds api - better exceptions
This commit is contained in:
parent
c2c2e4bac9
commit
488d620f40
6 changed files with 73 additions and 26 deletions
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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)]
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,7 @@ class BranchNotFound(ClientError):
|
||||||
|
|
||||||
class NotFoundOnGithub(ClientError):
|
class NotFoundOnGithub(ClientError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BranchNotSupported(ClientError):
|
||||||
|
pass
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue