Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Script to create entries from the CI. #19

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ jobs:
poetry install
- name: Check schema matches model
run: |
poetry run python ceda_status_validator/generate_schema.py
poetry run python -m ceda_status_validator.generate_schema
diff generated_schema.json statuspage.schema.json
- name: Run the validator
run: |
poetry run python ceda_status_validator/validate.py
poetry run python -m ceda_status_validator.validate
Empty file.
92 changes: 92 additions & 0 deletions ceda_status_validator/editor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import datetime as dt
import json

import click

from . import model


@click.group()
def cli() -> None:
"""Script to help manage entries on the status page."""


@cli.command()
@click.option(
"--status",
prompt=True,
type=click.Choice(list(model.Status.__members__.keys())),
default=model.Status.DOWN._name_,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to use the Enum's ._name_ rather than using .name?

help="Current status of this service to be shown to users.",
)
@click.option(
"--affected-services",
prompt=True,
type=str,
help="Free-text list of services which are affected. E.g. 'sof storage'. Keep it short!",
)
@click.option(
"--summary",
prompt=True,
type=str,
help="One-list summary of the problem. Keep it short!",
)
@click.option(
"--details",
prompt=True,
type=str,
help="Longer summary of the problem. Still keep it short!",
)
@click.option(
"--start-date",
prompt=True,
type=click.DateTime(
formats=[
"%d/%m/%Y %H:%M",
"%Y-%m-%d",
"%Y%m%d",
"%Y-%m-%dT%H:%M:%S",
"%Y-%m-%d %H:%M:%S",
Comment on lines +48 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to accept times with seconds if the model is going to strip them out to this format anyway: "%Y-%m-%dT%H:%M"?

"%Y-%m-%d %H:%M",
]
),
# Round down to nearest hour
default=dt.datetime.now().replace(minute=0, second=0, microsecond=0),
help="Time this incident will be shown to have started, and time of the first update.",
)
def add_entry(
status: str,
affected_services: str,
start_date: dt.datetime,
details: str,
summary: str,
) -> None:
"""Update status.json with an extra incident."""
# Create a new incident from CLI data.
update = model.Update(
date=start_date,
details=details,
)
incident = model.Incident(
status=model.Status[status].value,
affectedServices=affected_services,
summary=summary,
date=start_date,
updates=[update],
)

# Read in existing file and parse it.
with open("status.json", "r", encoding="utf-8") as thefile:
status_file_contents = json.load(thefile)
status_file_model = model.StatusPage.validate(status_file_contents)

# Add in the new incident.
status_file_model.root.append(incident)

# Write the file back.
with open("status.json", "w", encoding="utf-8") as thefile:
thefile.write(status_file_model.model_dump_json(indent=2, exclude_none=True))


if __name__ == "__main__":
cli()
2 changes: 1 addition & 1 deletion ceda_status_validator/generate_schema.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json

from model import StatusPage
from .model import StatusPage

schema = StatusPage.model_json_schema()

Expand Down
28 changes: 23 additions & 5 deletions ceda_status_validator/model.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import datetime as dt
import typing
from enum import Enum
from typing import Annotated, Any, Optional

from pydantic import AfterValidator, BaseModel, Field, HttpUrl, RootModel
import pydantic
from pydantic import BaseModel, Field, HttpUrl, RootModel

INPUT_DATE_FORMAT = "%Y-%m-%dT%H:%M"

T = typing.TypeVar("T")

def check_date_format(value: str) -> dt.datetime:

def check_date_format(value: T) -> T:
"""Makes sure the datetime format matches what the MOTD and CEDA status page expect"""
return dt.datetime.strptime(value, INPUT_DATE_FORMAT)
if isinstance(value, str):
dt.datetime.strptime(value, INPUT_DATE_FORMAT)
return value


class Status(Enum):
Expand All @@ -24,20 +30,32 @@ class Status(Enum):
class Update(BaseModel):
"""An update contains further details and an optional URL for more info"""

date: Annotated[str, AfterValidator(check_date_format)]
date: Annotated[dt.datetime, pydantic.BeforeValidator(check_date_format)]
details: str
url: Optional[HttpUrl] = None

@pydantic.field_serializer("date")
def serialize_date(
self, date: dt.datetime, _info: pydantic.FieldSerializationInfo
) -> str:
return str(date.strftime(INPUT_DATE_FORMAT))


class Incident(BaseModel):
"""An incident contains details and a list of updates associated with it"""

status: Status
affectedServices: str
summary: str
date: Annotated[str, AfterValidator(check_date_format)]
date: Annotated[dt.datetime, pydantic.BeforeValidator(check_date_format)]
updates: list[Update] = Field(min_length=1)

@pydantic.field_serializer("date")
def serialize_date(
self, date: dt.datetime, _info: pydantic.FieldSerializationInfo
) -> str:
return str(date.strftime(INPUT_DATE_FORMAT))


class StatusPage(RootModel[Any]):
"""The root of the status page object, containing a list of incidents"""
Expand Down
3 changes: 2 additions & 1 deletion ceda_status_validator/validate.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import sys

from model import StatusPage
from pydantic import ValidationError

from .model import StatusPage

with open("status.json", "r") as status_file:
status_page = status_file.read()

Expand Down
Loading
Loading