feat: First attempt deployment.
parent
987129920e
commit
1fd616ee53
|
@ -160,3 +160,6 @@ cython_debug/
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
|
|
||||||
|
test-data
|
||||||
|
build
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
# REF https://github.com/scratchmex/poetry-docker-template/blob/main/dockerfile.jinja
|
||||||
|
|
||||||
|
FROM python:3.11-slim-bookworm as base-python
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Build Variables
|
||||||
|
####################
|
||||||
|
ARG PIP_DISABLE_PIP_VERSION_CHECK 1
|
||||||
|
ARG PIP_NO_CACHE_DIR 1
|
||||||
|
## Disable Non-Reproducible pip
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Environment Variables
|
||||||
|
####################
|
||||||
|
ENV PYTHONOPTIMIZE 1
|
||||||
|
## Strip __debug__
|
||||||
|
|
||||||
|
ENV PYTHONUNBUFFERED 1
|
||||||
|
## Disable stdout/stderr Buffering
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Stage: Builder
|
||||||
|
####################
|
||||||
|
FROM base-python AS base-builder
|
||||||
|
WORKDIR /app-src
|
||||||
|
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Stage: Production
|
||||||
|
####################
|
||||||
|
FROM base-python as production
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install --no-install-recommends -y \
|
||||||
|
curl
|
||||||
|
|
||||||
|
RUN groupadd -g 1500 pyrunner && \
|
||||||
|
useradd -m -u 1500 -g pyrunner pyrunner
|
||||||
|
|
||||||
|
COPY --chown=pyrunner:pyrunner ./app /app
|
||||||
|
COPY --from=builder-base --chown=poetry:poetry /app-src/.venv /app/.venv
|
||||||
|
|
||||||
|
USER poetry
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
HEALTHCHECK CMD curl --fail http://localhost:3000 || exit 1
|
||||||
|
|
||||||
|
CMD poetry run uvicorn --proxy-headers --host=0.0.0.0 --port=3000 app.main:app
|
113
app/main.py
113
app/main.py
|
@ -1,16 +1,38 @@
|
||||||
|
import tomllib
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import enum
|
||||||
|
from pydantic import BaseModel, SecretStr, constr
|
||||||
import secrets
|
import secrets
|
||||||
from fastapi import FastAPI, HTTPException, Security
|
from fastapi import FastAPI, HTTPException, Security
|
||||||
from fastapi.openapi.models import APIKey
|
from fastapi.openapi.models import APIKey
|
||||||
|
from fastapi.security.api_key import APIKeyHeader
|
||||||
|
from starlette.status import HTTP_403_FORBIDDEN
|
||||||
|
import sys
|
||||||
|
|
||||||
|
class Settings(BaseModel):
|
||||||
|
api_key: SecretStr
|
||||||
|
path_support_reports: Path
|
||||||
|
|
||||||
|
with open(Path(__file__).parent / 'settings.toml', 'r') as f:
|
||||||
|
settings_dict = tomllib.loads(f.read())
|
||||||
|
SETTINGS = Settings(**settings_dict)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Utilities
|
# - Utilities
|
||||||
####################
|
####################
|
||||||
|
api_key_header = APIKeyHeader(
|
||||||
|
name="access_token",
|
||||||
|
auto_error=False,
|
||||||
|
)
|
||||||
|
|
||||||
async def g_api_key(
|
async def g_api_key(
|
||||||
api_key_header: APIKey = Security(api_key_header),
|
api_key_header: APIKey = Security(api_key_header),
|
||||||
) -> APIKey:
|
) -> APIKey:
|
||||||
if api_key_header and secrets.compare_digest(
|
if api_key_header and secrets.compare_digest(
|
||||||
str(api_key_header),
|
str(api_key_header),
|
||||||
settings.api_key.get_secret_value(),
|
SETTINGS.api_key.get_secret_value(),
|
||||||
):
|
):
|
||||||
return api_key_header
|
return api_key_header
|
||||||
|
|
||||||
|
@ -22,20 +44,93 @@ async def g_api_key(
|
||||||
####################
|
####################
|
||||||
# - Types
|
# - Types
|
||||||
####################
|
####################
|
||||||
class PInstSupCategory(Enum)
|
class PyInstSupTag(enum.StrEnum):
|
||||||
|
INST_WIN_OFFICIAL = enum.auto()
|
||||||
|
INST_WIN_WINSTORE = enum.auto()
|
||||||
|
INST_MAC_OFFICIAL = enum.auto()
|
||||||
|
INST_MAC_HOMEBREW = enum.auto()
|
||||||
|
INST_LIN_OFFICIAL = enum.auto()
|
||||||
|
INST_LIN_PKG = enum.auto()
|
||||||
|
COURSE_MATH1A = enum.auto()
|
||||||
|
COURSE_INTROPROG = enum.auto()
|
||||||
|
ENV_PIP = enum.auto()
|
||||||
|
ENV_CONDA = enum.auto()
|
||||||
|
IDE_VSCODE = enum.auto()
|
||||||
|
IDE_PYCHARM = enum.auto()
|
||||||
|
IDE_SPYDER = enum.auto()
|
||||||
|
IDE_NOTEPADPP = enum.auto()
|
||||||
|
|
||||||
|
class ClockOption(enum.IntEnum):
|
||||||
|
CLOCK_IN = enum.auto()
|
||||||
|
CLOCK_OUT = enum.auto()
|
||||||
|
|
||||||
|
class AttendanceEntry(BaseModel):
|
||||||
|
at: datetime
|
||||||
|
status: ClockOption
|
||||||
|
|
||||||
|
class SupportEntry(BaseModel):
|
||||||
|
study_id: constr(pattern = r'^s[0-9]{6}$')
|
||||||
|
tags: list[PyInstSupTag]
|
||||||
|
description: str
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - FastAPI App
|
# - FastAPI App
|
||||||
####################
|
####################
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
prefix="/v1",
|
#prefix="/v1",
|
||||||
dependencies=[Security(g_api_key)],
|
dependencies=[Security(g_api_key)],
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.post("/report/support")
|
####################
|
||||||
async def report_support(
|
# - Support
|
||||||
category:
|
####################
|
||||||
description: str,
|
@app.get("/report/support")
|
||||||
) -> bool:
|
async def g_support_entry() -> list[SupportEntry]:
|
||||||
"""Report an instance of granted Python Installation support."""
|
"""Report an instance of granted Python Installation support."""
|
||||||
return await d_old_app_passes()
|
|
||||||
|
with open(SETTINGS.path_support_reports, 'r') as f:
|
||||||
|
return [
|
||||||
|
SupportEntry(support_entry_snippet)
|
||||||
|
for support_entry_snippet in f.readlines()
|
||||||
|
]
|
||||||
|
|
||||||
|
@app.post("/report/support")
|
||||||
|
async def mk_support_entry(
|
||||||
|
support_entry: SupportEntry,
|
||||||
|
) -> bool:
|
||||||
|
"""Print granted Python Support."""
|
||||||
|
|
||||||
|
with open(SETTINGS.path_support_reports, 'a') as f:
|
||||||
|
print(
|
||||||
|
json.dumps(support_entry.model_dump()),
|
||||||
|
file = f,
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Hours
|
||||||
|
####################
|
||||||
|
@app.get("/report/hours")
|
||||||
|
async def g_hours_entry() -> list[SupportEntry]:
|
||||||
|
"""Print support attendance record."""
|
||||||
|
|
||||||
|
with open(SETTINGS.path_support_reports, 'r') as f:
|
||||||
|
return [
|
||||||
|
SupportEntry(support_entry_snippet)
|
||||||
|
for support_entry_snippet in f.readlines()
|
||||||
|
]
|
||||||
|
|
||||||
|
@app.post("/report/hours")
|
||||||
|
async def mk_hours_entry(
|
||||||
|
attendance_entry: AttendanceEntry,
|
||||||
|
) -> bool:
|
||||||
|
"""Clock in / out to Python support."""
|
||||||
|
|
||||||
|
with open(SETTINGS.path_support_reports, 'a') as f:
|
||||||
|
print(
|
||||||
|
json.dumps(attendance_entry.model_dump()),
|
||||||
|
file = f,
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
api_key = "TYJ80udNGIoOzIfKN3C66M732VI0Oh3jANqpcuxDRFQ="
|
||||||
|
path_support_reports = "/data/support_reports.json"
|
|
@ -1,24 +1,26 @@
|
||||||
[tool.poetry]
|
[project]
|
||||||
name = "docker-mailserver-gate"
|
name = "support-ticker"
|
||||||
version = "0.0.1"
|
version = "0.1.0"
|
||||||
description = "Low-level API abstraction of setup.sh in docker-mailserver, accessible by API key."
|
description = "Little tracker for support given as Python Installation Support."
|
||||||
authors = ["Sofus Albert Høgsbro Rose <sofus@dtumasters.org>"]
|
#authors = ["Sofus Albert Høgsbro Rose <sofus@sofusrose.com>"]
|
||||||
license = "AGPL3+"
|
#license = "AGPL3"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
requires-python = "==3.11.*"
|
||||||
python = "3.11.*"
|
dependencies = [
|
||||||
fastapi = "^0.94.0"
|
"fastapi ~= 0.94",
|
||||||
uvicorn = "^0.21.0"
|
"uvicorn ~= 0.21",
|
||||||
pydantic = {extras = ["email"], version = "^1.10.6"}
|
"pydantic[email] ~= 2.1",
|
||||||
docker = "^6.0.1"
|
]
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[project.optional-dependencies]
|
||||||
devtools = "^0.10.0"
|
dev = [
|
||||||
schemathesis = "^3.19.0"
|
"devtools ~= 0.10.0",
|
||||||
pre-commit = "^3.2.1"
|
"schemathesis ~= 3.19.0",
|
||||||
ruff = "^0.0.259"
|
"pre-commit ~= 3.2.1",
|
||||||
tan = "^22.12.1"
|
"ruff ~= 0.0.259",
|
||||||
mypy = "^1.1.1"
|
"tan ~= 22.12.1",
|
||||||
|
"mypy ~= 1.1.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,7 +65,7 @@ select = [
|
||||||
"Q", # flake8-quotes ## Finally - Quoting Style!
|
"Q", # flake8-quotes ## Finally - Quoting Style!
|
||||||
"PTH", # flake8-use-pathlib ## Enforce pathlib usage
|
"PTH", # flake8-use-pathlib ## Enforce pathlib usage
|
||||||
"A", # flake8-builtins ## Prevent Builtin Shadowing
|
"A", # flake8-builtins ## Prevent Builtin Shadowing
|
||||||
"C4", # flake8-comprehensions ## Check Compehension Appropriateness
|
"C4", # flake9-comprehensions ## Check Compehension Appropriateness
|
||||||
"DTZ", # flake8-datetimez ## Ban naive Datetime Creation
|
"DTZ", # flake8-datetimez ## Ban naive Datetime Creation
|
||||||
"EM", # flake8-errmsg ## Check Exception String Formatting
|
"EM", # flake8-errmsg ## Check Exception String Formatting
|
||||||
"ISC", # flake8-implicit-str-concat ## Enforce Good String Literal Concat
|
"ISC", # flake8-implicit-str-concat ## Enforce Good String Literal Concat
|
||||||
|
@ -129,5 +131,4 @@ warn_untyped_fields = true
|
||||||
# - Poetry Integration
|
# - Poetry Integration
|
||||||
####################
|
####################
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry>=1.0"]
|
requires = ["setuptools_scm[toml]>=6.2"]
|
||||||
build-backend = "poetry.masonry.api"
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
annotated-types==0.5.0
|
||||||
|
anyio==3.7.1
|
||||||
|
click==8.1.7
|
||||||
|
dnspython==2.4.2
|
||||||
|
email-validator==2.0.0.post2
|
||||||
|
fastapi==0.103.0
|
||||||
|
h11==0.14.0
|
||||||
|
idna==3.4
|
||||||
|
pydantic==2.3.0
|
||||||
|
pydantic_core==2.6.3
|
||||||
|
sniffio==1.3.0
|
||||||
|
starlette==0.27.0
|
||||||
|
support-ticker @ file:///app-root
|
||||||
|
typing_extensions==4.7.1
|
||||||
|
uvicorn==0.23.2
|
|
@ -0,0 +1,280 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
# Copyright (C) 2023 Sofus Albert Høgsbro Rose
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
if not all([
|
||||||
|
sys.version_info.major == 3,
|
||||||
|
sys.version_info.minor in [9, 10, 11, 12, 13],
|
||||||
|
]):
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import platform
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Constants
|
||||||
|
####################
|
||||||
|
SCRIPT_PATH = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
CMD_DEPENDENCIES = [
|
||||||
|
'podman',
|
||||||
|
'git',
|
||||||
|
'pre-commit'
|
||||||
|
]
|
||||||
|
|
||||||
|
IMAGE_NAME = "support-ticker"
|
||||||
|
IMAGE_VERSION = ("0", "1", "0")
|
||||||
|
|
||||||
|
REGISTRY_HOST = "git.sofus.io"
|
||||||
|
REGISTRY_USER = "so-rose"
|
||||||
|
REGISTRY_PASSWORD = subprocess.check_output(
|
||||||
|
['pass', 'services/home/git.sofus.io/container-registry-token']
|
||||||
|
).decode('ascii').strip()
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Help Text
|
||||||
|
####################
|
||||||
|
def action_help() -> None:
|
||||||
|
print("""This script provides one-click development, CI, and deployment.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
echo -e "./run.py [OPTION] [EXTRAS]"
|
||||||
|
|
||||||
|
The following commands must be available:
|
||||||
|
podman
|
||||||
|
=> The project is developed and run in podman containers.
|
||||||
|
git
|
||||||
|
=> This project uses git for versioning, and collaboration.
|
||||||
|
pre-commit
|
||||||
|
=> Enforces that certain checks pass at each commit.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
./run.sh
|
||||||
|
=> Equivilant to run.sh help.
|
||||||
|
|
||||||
|
./run.sh -h|h|help
|
||||||
|
=> Shows this text.
|
||||||
|
|
||||||
|
Options, Run Locally:
|
||||||
|
./run.sh dev
|
||||||
|
=> Will run the app on port 3000, for local development.
|
||||||
|
|
||||||
|
Options, Check:
|
||||||
|
./run.sh check
|
||||||
|
=> Will run all checks, including static analysis and tests.
|
||||||
|
|
||||||
|
./run.sh analyze-quality [OPTIONS]
|
||||||
|
=> Will run the static code-quality analysis suite.
|
||||||
|
=> OPTIONS will be passed directly to the tool (ruff).
|
||||||
|
|
||||||
|
./run.sh analyze-types [OPTIONS]
|
||||||
|
=> Will run the static type checking suite.
|
||||||
|
=> OPTIONS will be passed directly to the tool (pyright).
|
||||||
|
|
||||||
|
./run.sh analyze-security
|
||||||
|
=> Will run the static security analysis suite.
|
||||||
|
|
||||||
|
./run.sh analyze-format [--overwrite] [OPTIONS]
|
||||||
|
=> Will run the code formatter in read-only omde.
|
||||||
|
=> '--overwrite' will cause the formater to reformat all files.
|
||||||
|
=> OPTIONS will be passed directly to the tool (tan).
|
||||||
|
|
||||||
|
./run.sh test [OPTIONS]
|
||||||
|
=> Will run the test suite locally.
|
||||||
|
=> OPTIONS will be passed directly to the tool (pytest).
|
||||||
|
|
||||||
|
Options, Build & Deploy:
|
||||||
|
./run.sh build
|
||||||
|
=> Will build, tag and upload a docker image appropriately.
|
||||||
|
|
||||||
|
./run.sh build-release
|
||||||
|
=> Will build, tag and upload a docker image appropriate for release.
|
||||||
|
|
||||||
|
Options, Housekeeping:
|
||||||
|
./run.sh clean
|
||||||
|
=> Will delete all data caused by this project's presence on your system.
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Utilities
|
||||||
|
####################
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def cd_script_dir() -> None:
|
||||||
|
cwd_orig = Path.cwd()
|
||||||
|
|
||||||
|
os.chdir(SCRIPT_PATH)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
os.chdir(cwd_orig)
|
||||||
|
|
||||||
|
def get_git_revision_hash(short = True) -> str:
|
||||||
|
commit_id = subprocess.check_output(
|
||||||
|
['git', 'rev-parse', 'HEAD']
|
||||||
|
).decode('ascii').strip()
|
||||||
|
|
||||||
|
return commit_id[:16] if short else commit_id
|
||||||
|
|
||||||
|
def cmd_exists(cmd: str) -> bool:
|
||||||
|
"""Checks if a command exists. Supports Linux, Mac, Windows.
|
||||||
|
"""
|
||||||
|
return shutil.which(cmd) is not None
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Actions
|
||||||
|
####################
|
||||||
|
def action_build() -> None:
|
||||||
|
subprocess.run([
|
||||||
|
"podman", "build",
|
||||||
|
".",
|
||||||
|
|
||||||
|
# :<commit_id> - Tag Commit ID
|
||||||
|
"--tag",
|
||||||
|
f"{IMAGE_NAME}:{get_git_revision_hash()}",
|
||||||
|
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Actions - Runners
|
||||||
|
####################
|
||||||
|
def action_run(image_override = None, cli_cmd = None) -> None:
|
||||||
|
if cli_cmd is None:
|
||||||
|
cli_cmd = sys.argv[2:]
|
||||||
|
|
||||||
|
if image_override is not None:
|
||||||
|
image_name = image_override
|
||||||
|
else:
|
||||||
|
image_name = IMAGE_NAME + ":dev"
|
||||||
|
|
||||||
|
subprocess.run([
|
||||||
|
"podman", "run", "--rm", "-it",
|
||||||
|
"--volume", ".:/app-root",
|
||||||
|
"--volume", "./test-data:/data",
|
||||||
|
"--workdir", "/app-root",
|
||||||
|
"--publish", "3000:3000",
|
||||||
|
f"{image_name}",
|
||||||
|
*cli_cmd,
|
||||||
|
])
|
||||||
|
|
||||||
|
def action_dev() -> None:
|
||||||
|
action_run(
|
||||||
|
image_override="docker.io/python:3.11-slim-bookworm",
|
||||||
|
cli_cmd = [
|
||||||
|
"bash", "-xc", """
|
||||||
|
if [ ! -d .venv ]; then
|
||||||
|
python -m venv .venv
|
||||||
|
. .venv/bin/activate
|
||||||
|
pip install .
|
||||||
|
pip freeze > requirements.txt
|
||||||
|
deactivate
|
||||||
|
fi
|
||||||
|
. .venv/bin/activate
|
||||||
|
uvicorn --host=0.0.0.0 --port=3000 app.main:app --reload"""
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Actions - Helpers
|
||||||
|
####################
|
||||||
|
def action_update_deps() -> None:
|
||||||
|
action_run(
|
||||||
|
image_override="docker.io/python:3.11-slim-bookworm",
|
||||||
|
cli_cmd = [
|
||||||
|
"bash", "-c", """
|
||||||
|
if [ ! -d .venv ]; then
|
||||||
|
python -m venv .venv
|
||||||
|
fi
|
||||||
|
. .venv/bin/activate
|
||||||
|
pip install .
|
||||||
|
pip freeze > requirements.txt"""
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Actions - Publish
|
||||||
|
####################
|
||||||
|
def action_publish() -> None:
|
||||||
|
action_build()
|
||||||
|
|
||||||
|
subprocess.run([
|
||||||
|
"podman", "login", REGISTRY_HOST,
|
||||||
|
"--username", REGISTRY_USER,
|
||||||
|
"--password", REGISTRY_PASSWORD,
|
||||||
|
])
|
||||||
|
|
||||||
|
# Publish Image @ :<commit_id>
|
||||||
|
subprocess.run([
|
||||||
|
"podman", "image", "push",
|
||||||
|
f"{IMAGE_NAME}:{get_git_revision_hash()}",
|
||||||
|
f"{REGISTRY_HOST}/{REGISTRY_USER}/{IMAGE_NAME}:{get_git_revision_hash()}",
|
||||||
|
])
|
||||||
|
|
||||||
|
# Publish Image @ :M, :M.m, :M.m.p
|
||||||
|
for image_tag in [
|
||||||
|
f"{IMAGE_NAME}:{'.'.join(IMAGE_VERSION)}",
|
||||||
|
f"{IMAGE_NAME}:{'.'.join(IMAGE_VERSION[:2])}",
|
||||||
|
f"{IMAGE_NAME}:{IMAGE_VERSION[0]}",
|
||||||
|
]:
|
||||||
|
subprocess.run([
|
||||||
|
"podman", "tag",
|
||||||
|
f"{IMAGE_NAME}:{get_git_revision_hash()}",
|
||||||
|
image_tag
|
||||||
|
])
|
||||||
|
|
||||||
|
# Publish Image
|
||||||
|
subprocess.run([
|
||||||
|
"podman", "image", "push",
|
||||||
|
image_tag,
|
||||||
|
f"{REGISTRY_HOST}/{REGISTRY_USER}/{image_tag}",
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Script
|
||||||
|
####################
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Check Available Commands
|
||||||
|
for cmd in CMD_DEPENDENCIES:
|
||||||
|
if not cmd_exists(cmd) :
|
||||||
|
print("One or more dependencies are not installed. Please see --help.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
with cd_script_dir():
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
{
|
||||||
|
"dev": action_dev,
|
||||||
|
|
||||||
|
"update-deps": action_update_deps,
|
||||||
|
|
||||||
|
"dev": action_dev,
|
||||||
|
|
||||||
|
"build": action_build,
|
||||||
|
"publish": action_publish,
|
||||||
|
|
||||||
|
"help": action_help,
|
||||||
|
"-h": action_help,
|
||||||
|
"--help": action_help,
|
||||||
|
}[sys.argv[1]]()
|
||||||
|
else:
|
||||||
|
action_help()
|
Loading…
Reference in New Issue