Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ keys:
the latest such build is stored in the state file and used as the new
``since`` value for the respective CI system on subsequent runs.

``until``
*(optional)* A timestamp (date, time, & timezone); only assets for builds
started before the given point in time will be retrieved

``types``
A list of build trigger event types; only assets for builds triggered by
one of the given events will be retrieved
Expand Down
4 changes: 3 additions & 1 deletion src/tinuous/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ def fetch(cfg: Config, state: str, sanitize_secrets: bool) -> None:
since = datetime.fromisoformat(since_stamps[name])
except KeyError:
since = cfg.since
ci = cicfg.get_system(repo=cfg.repo, since=since, tokens=tokens[name])
ci = cicfg.get_system(
repo=cfg.repo, since=since, until=cfg.until, tokens=tokens[name]
)
for obj in ci.get_build_assets(cfg.types, artifacts=get_artifacts):
if isinstance(obj, BuildLog):
path = obj.expand_path(cicfg.path, cfg.vars)
Expand Down
6 changes: 5 additions & 1 deletion src/tinuous/appveyor.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ def get_builds(self) -> Iterator[dict]:
def get_build_assets(
self, event_types: List[EventType], artifacts: bool = False # noqa: U100
) -> Iterator["BuildAsset"]:
log.info("Fetching runs newer than %s", self.since)
log.info("Fetching builds newer than %s", self.since)
if self.until is not None:
log.info("Skipping builds newer than %s", self.until)
for build in self.get_builds():
if build.get("pullRequestId"):
run_event = EventType.PULL_REQUEST
Expand All @@ -62,6 +64,8 @@ def get_build_assets(
ts = isoparse(build["created"])
if ts <= self.since:
break
elif self.until is not None and ts > self.until:
log.info("Build %s is too new; skipping", build["buildNumber"])
elif build.get("finished") is None:
log.info("Build %s not completed; skipping", build["buildNumber"])
self.register_build(ts, False)
Expand Down
1 change: 1 addition & 0 deletions src/tinuous/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class CISystem(ABC, BaseModel):
repo: str
token: str
since: datetime
until: Optional[datetime]
fetched: List[Tuple[datetime, bool]] = Field(default_factory=list)

@staticmethod
Expand Down
41 changes: 33 additions & 8 deletions src/tinuous/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Dict, Iterator, List, Optional, Pattern, Tuple

from pydantic import BaseModel, Field, validator
from pydantic.fields import ModelField

from .appveyor import Appveyor
from .base import CISystem, EventType
Expand All @@ -27,7 +28,11 @@ def get_auth_tokens() -> Dict[str, str]:

@abstractmethod
def get_system(
self, repo: str, since: datetime, tokens: Dict[str, str]
self,
repo: str,
since: datetime,
until: Optional[datetime],
tokens: Dict[str, str],
) -> CISystem:
... # pragma: no cover

Expand All @@ -42,11 +47,16 @@ def get_auth_tokens() -> Dict[str, str]:
return GitHubActions.get_auth_tokens()

def get_system(
self, repo: str, since: datetime, tokens: Dict[str, str]
self,
repo: str,
since: datetime,
until: Optional[datetime],
tokens: Dict[str, str],
) -> GitHubActions:
return GitHubActions(
repo=repo,
since=since,
until=until,
token=tokens["github"],
workflows=self.workflows,
)
Expand All @@ -57,10 +67,17 @@ class TravisConfig(CIConfig):
def get_auth_tokens() -> Dict[str, str]:
return Travis.get_auth_tokens()

def get_system(self, repo: str, since: datetime, tokens: Dict[str, str]) -> Travis:
def get_system(
self,
repo: str,
since: datetime,
until: Optional[datetime],
tokens: Dict[str, str],
) -> Travis:
return Travis(
repo=repo,
since=since,
until=until,
token=tokens["travis"],
gh_token=tokens["github"],
)
Expand All @@ -75,11 +92,16 @@ def get_auth_tokens() -> Dict[str, str]:
return Appveyor.get_auth_tokens()

def get_system(
self, repo: str, since: datetime, tokens: Dict[str, str]
self,
repo: str,
since: datetime,
until: Optional[datetime],
tokens: Dict[str, str],
) -> Appveyor:
return Appveyor(
repo=repo,
since=since,
until=until,
token=tokens["appveyor"],
accountName=self.accountName,
projectSlug=self.projectSlug,
Expand Down Expand Up @@ -110,6 +132,7 @@ class Config(NoExtraModel):
vars: Dict[str, str] = Field(default_factory=dict)
ci: CIConfigDict
since: datetime
until: Optional[datetime] = None
types: List[EventType]
secrets: Dict[str, Pattern] = Field(default_factory=dict)
allow_secrets_regex: Optional[Pattern] = Field(None, alias="allow-secrets-regex")
Expand All @@ -121,8 +144,10 @@ def _validate_repo(cls, v: str) -> str: # noqa: B902, U100
raise ValueError("Repo must be in the form 'OWNER/NAME'")
return v

@validator("since")
def _validate_since(cls, v: datetime) -> datetime: # noqa: B902, U100
if v.tzinfo is None:
raise ValueError("'since' timestamp must include timezone offset")
@validator("since", "until")
def _validate_datetimes(
cls, v: Optional[datetime], field: ModelField # noqa: B902, U100
) -> Optional[datetime]:
if v is not None and v.tzinfo is None:
raise ValueError(f"{field.name!r} timestamp must include timezone offset")
return v
8 changes: 7 additions & 1 deletion src/tinuous/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,17 @@ def get_build_assets(
self, event_types: List[EventType], artifacts: bool = False
) -> Iterator["BuildAsset"]:
log.info("Fetching runs newer than %s", self.since)
if self.until is not None:
log.info("Skipping runs newer than %s", self.until)
for wf in self.get_workflows():
log.info("Fetching runs for workflow %s (%s)", wf.path, wf.name)
for run in wf.get_runs():
run_event = EventType.from_gh_event(run.event)
ts = ensure_aware(run.created_at)
if ts <= self.since:
break
elif self.until is not None and ts > self.until:
log.info("Run %s is too new; skipping", run.run_number)
elif run.status != "completed":
log.info("Run %s not completed; skipping", run.run_number)
self.register_build(ts, False)
Expand Down Expand Up @@ -146,6 +150,8 @@ def get_artifacts(self, run: WorkflowRun) -> Iterator[Tuple[str, str]]:

def get_release_assets(self) -> Iterator["GHReleaseAsset"]:
log.info("Fetching releases newer than %s", self.since)
if self.until is not None:
log.info("Skipping releases newer than %s", self.until)
for rel in self.ghrepo.get_releases():
if rel.draft:
log.info("Release %s is draft; skipping", rel.tag_name)
Expand All @@ -154,7 +160,7 @@ def get_release_assets(self) -> Iterator["GHReleaseAsset"]:
log.info("Release %s is prerelease; skipping", rel.tag_name)
continue
ts = ensure_aware(rel.published_at)
if ts <= self.since:
if ts <= self.since or (self.until is not None and ts > self.until):
continue
self.register_build(ts, True) # TODO: Set to False for drafts?
log.info("Found release %s", rel.tag_name)
Expand Down
4 changes: 4 additions & 0 deletions src/tinuous/travis.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ def get_build_assets(
self, event_types: List[EventType], artifacts: bool = False # noqa: U100
) -> Iterator["BuildAsset"]:
log.info("Fetching builds newer than %s", self.since)
if self.until is not None:
log.info("Skipping builds newer than %s", self.until)
for build in self.paginate(
f"/repo/{quote(self.repo, safe='')}/builds",
params={"include": "build.jobs"},
Expand All @@ -93,6 +95,8 @@ def get_build_assets(
ts = isoparse(build["started_at"])
if ts <= self.since:
break
elif self.until is not None and ts > self.until:
log.info("Build %s is too new; skipping", build["number"])
elif build["finished_at"] is None:
log.info("Build %s not completed; skipping", build["number"])
self.register_build(ts, False)
Expand Down