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
5 changes: 5 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ jobs:
run: |
pip install -e .

- name: ruff
run: |
ruff format --check ./ctf
ruff check ./ctf

- name: ctf init
run: |
ctf init test-ctf
Expand Down
14 changes: 14 additions & 0 deletions ctf/__main__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#!/usr/bin/env python3
import logging
import os

import typer
from typer import Typer
from typing_extensions import Annotated

from ctf import (
CTF_ROOT_DIRECTORY,
Expand Down Expand Up @@ -39,6 +42,17 @@
app.add_typer(version_app)


@app.callback()
def global_options(
verbose: Annotated[
bool, typer.Option("--verbose", "-v", help="Enable DEBUG logging.")
] = False,
):
if verbose:
LOG.setLevel(logging.DEBUG)
LOG.handlers[0].setLevel(logging.DEBUG)


def main():
app()

Expand Down
7 changes: 4 additions & 3 deletions ctf/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
from enum import StrEnum, unique

import rich
import typer
import yaml
from typing_extensions import Annotated
Expand Down Expand Up @@ -66,12 +67,12 @@ def flags(
return

if format == OutputFormat.JSON:
print(json.dumps(obj=flags, indent=2))
rich.print(json.dumps(obj=flags, indent=2))
elif format == OutputFormat.CSV:
output = io.StringIO()
writer = csv.DictWriter(f=output, fieldnames=flags[0].keys())
writer.writeheader()
writer.writerows(rowdicts=flags)
print(output.getvalue())
rich.print(output.getvalue())
elif format == OutputFormat.YAML:
print(yaml.safe_dump(data=flags))
rich.print(yaml.safe_dump(data=flags))
29 changes: 13 additions & 16 deletions ctf/list.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import os
from enum import StrEnum

import rich
import typer
from tabulate import tabulate
from rich.table import Table
from typing_extensions import Annotated

from ctf import CTF_ROOT_DIRECTORY
from ctf.logger import LOG
from ctf.utils import parse_post_yamls, parse_track_yaml

app = typer.Typer()
Expand Down Expand Up @@ -52,19 +52,16 @@ def list_tracks(
)

if format.value == "pretty":
LOG.info(
"\n"
+ tabulate(
parsed_tracks,
headers=[
"Internal track name",
"Discourse Topic Name",
"Dev",
"Support",
"QA",
],
tablefmt="fancy_grid",
)
)
table = Table(title="Tracks")
table.add_column("Internal track name", style="cyan")
table.add_column("Discourse topic name", style="magenta")
table.add_column("Dev")
table.add_column("Support")
table.add_column("QA")

for parsed_track in sorted(parsed_tracks, key=lambda x: x[0].lower()):
table.add_row(*parsed_track)

rich.print(table)
else:
raise ValueError(f"Invalid format: {format.value}")
12 changes: 9 additions & 3 deletions ctf/logger.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import logging

import coloredlogs
from rich.logging import RichHandler

LOG = logging.getLogger()
LOG.setLevel(level=logging.DEBUG)
coloredlogs.install(level="DEBUG", logger=LOG)
LOG.setLevel(level=logging.INFO)
FORMAT = "%(message)s"
logging.basicConfig(
level=logging.DEBUG,
format=FORMAT,
datefmt="[%X]",
handlers=[RichHandler(level=logging.INFO)],
)
7 changes: 6 additions & 1 deletion ctf/new.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ def new(
],
template: Annotated[
Template,
typer.Option("--template", "-t", help="Template to use for the track."),
typer.Option(
"--template",
"-t",
help="Template to use for the track.",
prompt="Template to use for the track.",
),
] = Template.APACHE_PHP,
force: Annotated[
bool,
Expand Down
3 changes: 2 additions & 1 deletion ctf/services.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os

import rich
import typer
from typing_extensions import Annotated

Expand Down Expand Up @@ -49,4 +50,4 @@ def services(
check = service["check"]
port = service["port"]

print(f"{track}/{instance}/{name} {contact} {address} {check} {port}")
rich.print(f"{track}/{instance}/{name} {contact} {address} {check} {port}")
5 changes: 3 additions & 2 deletions ctf/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import subprocess
from datetime import datetime

import rich
import typer
from typing_extensions import Annotated

Expand Down Expand Up @@ -151,7 +152,7 @@ def stats(
sorted(stats["number_of_points_per_track"].items(), key=lambda item: item[1])
)

print(json.dumps(stats, indent=2, ensure_ascii=False))
rich.print(json.dumps(stats, indent=2, ensure_ascii=False))
if generate_badges:
if not _has_pybadges:
LOG.critical(msg="Module pybadges was not found.")
Expand Down Expand Up @@ -291,7 +292,7 @@ def stats(
# Execute your command here (replace with what you need)
result = (
subprocess.run(
["python", "scripts/ctf.py", "stats"],
["ctf", "stats"],
check=False,
capture_output=True,
text=True,
Expand Down
1 change: 1 addition & 0 deletions ctf/templates/init/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ challenges/*/terraform/versions.tf
!.vscode/settings.json
!.vscode/extensions.json

.idea
4 changes: 2 additions & 2 deletions ctf/templates/init/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"editor.tabSize": 2
},
"yaml.schemas": {
"scripts/schemas/track.yaml.json": "challenges/**/track.yaml",
"scripts/schemas/post.json": "challenges/*/posts/*.yaml"
"schemas/track.yaml.json": "challenges/*/track.yaml",
"schemas/post.json": "challenges/*/posts/*.yaml"
}
}
143 changes: 143 additions & 0 deletions ctf/templates/init/schemas/post.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "",
"type": "object",
"properties": {
"type": {
"description": "Type of post. Use `topic` for the initial message of a track, and use `post` for subsequent posts (for example posts after a flag is submitted). `posts` is for multiple posts in one file (post).",
"type": "string",
"enum": [
"post",
"posts",
"topic"
]
},
"topic": {
"description": "The discourse topic to post this post into. For a given track, this should always have the same value across posts.",
"type": "string",
"minLength": 1
},
"trigger": {
"description": "Defines a custom trigger to post a message after a specific action happened, for example a flag is submitted.",
"type": "object",
"properties": {
"type": {
"description": "Type of trigger: use `flag` to post after a given flag is submitted (the value of the flag should be set in the `tag` property.)",
"type": "string",
"enum": [
"flag"
]
},
"tag": {
"description": "Value of the trigger. For a flag trigger, this is the flag value.",
"type": "string",
"minLength": 1
}
},
"required": [
"type",
"tag"
]
},
"api": {
"type": "object",
"properties": {
"user": {
"description": "The discourse user this post is posted by.",
"type": "string",
"enum": ["nsec","system","theChief","theMuscle","theFace","theFixer","theMechanic"]
}
},
"required": [
"user"
]
},
"title": {
"description": "The discourse title of the topic. This should be the same across posts of the same track.",
"type": "string",
"minLength": 1
},
"body": {
"description": "Content of the post. Markdown is supported.",
"type": "string",
"minLength": 1
},
"posts": {
"description": "",
"type": "array",
"items": {
"type": "object",
"properties": {
"api": {
"type": "object",
"properties": {
"user": {
"description": "The discourse user this post is posted by.",
"type": "string",
"minLength": 1
}
},
"required": [
"user"
]
},
"body": {
"description": "Content of the post. Markdown is supported.",
"type": "string",
"minLength": 1
}
},
"required": [
"api",
"body"
]
}
}
},
"if": {
"properties": {
"type": {
"const": "topic"
}
}
},
"then": {
"required": [
"type",
"api",
"title",
"body"
]
},
"else": {
"if": {
"properties": {
"type": {
"const": "post"
}
}
},
"then": {
"required": [
"type",
"api",
"body"
]
},
"else": {
"if": {
"properties": {
"type": {
"const": "posts"
}
}
},
"then": {
"required": [
"type",
"posts"
]
}
}
}
}
Loading