48 Commits

Author SHA1 Message Date
Oscar Krause
6fbbf9f8a7 added meta tag for "Content-Security-Policy" 2024-02-27 08:21:40 +01:00
Oscar Krause
c7f5ae3d45 fixed layouts path 2023-07-04 13:45:27 +02:00
Oscar Krause
c461bed239 toggle api endpoints 2023-07-04 13:34:49 +02:00
Oscar Krause
a2db9ff446 typos 2023-07-04 13:34:49 +02:00
Oscar Krause
0cb1627447 added information about ipv6 may be must disabled 2023-07-04 13:34:49 +02:00
Oscar Krause
e8be60efb3 removed mysql from included docker drivers 2023-07-04 13:34:49 +02:00
Oscar Krause
db37bbc344 added docker command to logging section
thanks to @libreshare (https://gitea.publichub.eu/oscar.krause/fastapi-dls/issues/2)
2023-07-04 13:34:49 +02:00
Oscar Krause
117e66f0db improvements
thanks to @AbsolutelyFree (https://gitea.publichub.eu/oscar.krause/fastapi-dls/issues/1)
2023-07-04 13:34:49 +02:00
Oscar Krause
b298755faa fixed "deploy:pacman" 2023-07-04 13:34:49 +02:00
Oscar Krause
fb27616b93 fixed mariadb-client installation
ref. https://github.com/PyMySQL/mysqlclient/discussions/624
2023-07-04 13:34:49 +02:00
Oscar Krause
0a08b033ea added missing "pkg-config" for "mysqlclient==2.2.0"
ref. https://stackoverflow.com/questions/76533384/docker-alpine-build-fails-on-mysqlclient-installation-with-error-exception-can
2023-07-04 13:34:49 +02:00
Oscar Krause
8c6df6eb3a fixed versions 2023-07-04 13:34:49 +02:00
Oscar Krause
ef5eead1c3 refactored docker-compose.yml so very simple example, and moved proxy to "examples" directory 2023-07-04 13:34:49 +02:00
Oscar Krause
d8cae944e7 added 15.3 to supported drivers list 2023-07-04 13:34:49 +02:00
Oscar Krause
75a5ae7b29 updated compatibility list 2023-07-04 13:34:49 +02:00
Oscar Krause
5ff9d1a228 docker-compose.yml - added note for TZ 2023-07-04 13:34:49 +02:00
Oscar Krause
b46593f27e requirements.txt updated 2023-07-04 13:34:49 +02:00
Oscar Krause
16271ad00b implemented button for delete all expired leases 2023-06-12 11:41:01 +02:00
Oscar Krause
8d236af289 Merge branch 'dev' into ui
# Conflicts:
#	requirements.txt
2023-06-12 11:27:36 +02:00
Oscar Krause
50c996f199 fixed "x in y" statement 2023-01-23 07:43:39 +01:00
Oscar Krause
e0736b6199 fixed Origin.delete() 2023-01-23 07:19:18 +01:00
Oscar Krause
5a59864383 Merge remote-tracking branch 'origin/dev' into ui 2023-01-23 07:19:05 +01:00
Oscar Krause
024ca111a9 fixes 2023-01-18 12:04:42 +01:00
Oscar Krause
6dbd39b76c helper.js - added "lease_renewal" to table 2023-01-18 08:42:57 +01:00
Oscar Krause
21f49a2fb5 fixed return type 2023-01-18 08:42:38 +01:00
Oscar Krause
757bae849c fixed origin null pointer exception on leases table 2023-01-18 08:42:28 +01:00
Oscar Krause
e1259838db Merge branch 'dev' into ui
# Conflicts:
#	app/orm.py
2023-01-18 08:39:06 +01:00
Oscar Krause
4699c1770d Merge branch 'dev' into ui
# Conflicts:
#	app/main.py
2023-01-05 07:31:15 +01:00
Oscar Krause
0c5fb7566c PKGBUILD - fixed jinja dependency 2023-01-04 14:43:37 +01:00
Oscar Krause
995e959bb1 dashboard_origins.html - implemented confirmation before deleting all origins 2023-01-04 12:11:55 +01:00
Oscar Krause
7eb3d7434d implemented refresh button 2023-01-04 12:11:23 +01:00
Oscar Krause
efc8d9c564 display hostname as title on leases table 2023-01-04 12:00:15 +01:00
Oscar Krause
6ef1216db6 Merge branch 'dev' into ui
# Conflicts:
#	app/main.py
2023-01-04 11:19:32 +01:00
Oscar Krause
f48fc4ff98 display hostname as title on leases table 2023-01-04 11:17:00 +01:00
Oscar Krause
41cb7ae14f implemented delete origin endpoint for frontend 2023-01-04 09:52:51 +01:00
Oscar Krause
5035dd4947 fixed refreshing data after deleting some origin or lease 2023-01-04 09:52:15 +01:00
Oscar Krause
08440d242f implemented delete origin endpoint for frontend 2023-01-04 09:51:43 +01:00
Oscar Krause
f35a4c6145 fixed timezone handling 2023-01-03 21:49:24 +01:00
Oscar Krause
f199800040 sort leases by expires 2023-01-03 21:29:32 +01:00
Oscar Krause
ca96ae08a7 sort origins by hostname 2023-01-03 21:29:24 +01:00
Oscar Krause
49279b6b66 fixed origin table 2023-01-03 21:23:57 +01:00
Oscar Krause
d06e100bef .gitlab-ci.yml - rebuild docker image when requirements.txt has changed 2023-01-03 21:03:34 +01:00
Oscar Krause
c5e344b38a added jinja2 dependency 2023-01-03 21:01:29 +01:00
Oscar Krause
4b4979cf86 fixed imports and tests 2023-01-03 20:55:23 +01:00
Oscar Krause
139fdd3472 Merge branch 'dev' into ui 2023-01-03 20:45:51 +01:00
Oscar Krause
71c4972e6a added config values to dashboard 2023-01-03 15:03:09 +01:00
Oscar Krause
df43a598cd Merge branch 'dev' into ui 2023-01-03 14:55:26 +01:00
Oscar Krause
fc8ef174cd implemented basic ui 2023-01-02 18:01:44 +01:00
42 changed files with 847 additions and 544 deletions

View File

@@ -2,7 +2,7 @@ Package: fastapi-dls
Version: 0.0 Version: 0.0
Architecture: all Architecture: all
Maintainer: Oscar Krause oscar.krause@collinwebdesigns.de Maintainer: Oscar Krause oscar.krause@collinwebdesigns.de
Depends: python3, python3-fastapi, python3-uvicorn, python3-dotenv, python3-dateutil, python3-josepy, python3-sqlalchemy, python3-pycryptodome, python3-markdown, uvicorn, openssl Depends: python3, python3-fastapi, python3-uvicorn, python3-dotenv, python3-dateutil, python3-jose, python3-sqlalchemy, python3-pycryptodome, python3-markdown, python3-jinja2, uvicorn, openssl
Recommends: curl Recommends: curl
Installed-Size: 10240 Installed-Size: 10240
Homepage: https://git.collinwebdesigns.de/oscar.krause/fastapi-dls Homepage: https://git.collinwebdesigns.de/oscar.krause/fastapi-dls

View File

@@ -1,11 +0,0 @@
# https://packages.debian.org/hu/
fastapi==0.92.0
uvicorn[standard]==0.17.6
python-jose[pycryptodome]==3.3.0
pycryptodome==3.11.0
python-dateutil==2.8.2
sqlalchemy==1.4.46
markdown==3.4.1
python-dotenv==0.21.0
jinja2==3.1.2
httpx==0.23.3

View File

@@ -1,10 +0,0 @@
# https://packages.ubuntu.com
fastapi==0.101.0
uvicorn[standard]==0.27.1
python-jose[pycryptodome]==3.3.0
pycryptodome==3.20.0
python-dateutil==2.8.2
sqlalchemy==1.4.50
markdown==3.5.2
python-dotenv==1.0.1
jinja2==3.1.2

View File

@@ -1,10 +0,0 @@
# https://packages.ubuntu.com
fastapi==0.110.3
uvicorn[standard]==0.30.3
python-jose[pycryptodome]==3.3.0
pycryptodome==3.20.0
python-dateutil==2.9.0
sqlalchemy==2.0.32
markdown==3.6
python-dotenv==1.0.1
jinja2==3.1.3

View File

@@ -8,11 +8,11 @@ pkgdesc='NVIDIA DLS server implementation with FastAPI'
arch=('any') arch=('any')
url='https://git.collinwebdesigns.de/oscar.krause/fastapi-dls' url='https://git.collinwebdesigns.de/oscar.krause/fastapi-dls'
license=('MIT') license=('MIT')
depends=('python' 'python-jose' 'python-starlette' 'python-httpx' 'python-fastapi' 'python-dotenv' 'python-dateutil' 'python-sqlalchemy' 'python-pycryptodome' 'uvicorn' 'python-markdown' 'openssl') depends=('python' 'python-jose' 'python-starlette' 'python-httpx' 'python-fastapi' 'python-dotenv' 'python-dateutil' 'python-sqlalchemy' 'python-pycryptodome' 'python-jinja' 'uvicorn' 'python-markdown' 'openssl')
provider=("$pkgname") provider=("$pkgname")
install="$pkgname.install" install="$pkgname.install"
backup=('etc/default/fastapi-dls') backup=('etc/default/fastapi-dls')
source=("git+file://${CI_PROJECT_DIR}" source=('git+file:///builds/oscar.krause/fastapi-dls' # https://gitea.publichub.eu/oscar.krause/fastapi-dls.git
"$pkgname.default" "$pkgname.default"
"$pkgname.service" "$pkgname.service"
"$pkgname.tmpfiles") "$pkgname.tmpfiles")
@@ -37,17 +37,52 @@ check() {
} }
package() { package() {
# create directories
install -d "$pkgdir/usr/share/doc/$pkgname" install -d "$pkgdir/usr/share/doc/$pkgname"
install -d "$pkgdir/var/lib/$pkgname/cert" install -d "$pkgdir/var/lib/$pkgname/cert"
# copy docs & static files
cp -r "$srcdir/$pkgname/doc"/* "$pkgdir/usr/share/doc/$pkgname/" cp -r "$srcdir/$pkgname/doc"/* "$pkgdir/usr/share/doc/$pkgname/"
install -Dm644 "$srcdir/$pkgname/README.md" "$pkgdir/usr/share/doc/$pkgname/README.md" install -Dm644 "$srcdir/$pkgname/README.md" "$pkgdir/usr/share/doc/$pkgname/README.md"
install -Dm644 "$srcdir/$pkgname/version.env" "$pkgdir/usr/share/doc/$pkgname/version.env" install -Dm644 "$srcdir/$pkgname/version.env" "$pkgdir/usr/share/doc/$pkgname/version.env"
sed -i "s/README.md/\/usr\/share\/doc\/$pkgname\/README.md/g" "$srcdir/$pkgname/app/main.py" sed -i "s/README.md/\/usr\/share\/doc\/$pkgname\/README.md/g" "$srcdir/$pkgname/app/main.py"
# copy main app python files
sed -i "s/join(dirname(__file__), 'cert\//join('\/var\/lib\/$pkgname', 'cert\//g" "$srcdir/$pkgname/app/main.py" sed -i "s/join(dirname(__file__), 'cert\//join('\/var\/lib\/$pkgname', 'cert\//g" "$srcdir/$pkgname/app/main.py"
install -Dm755 "$srcdir/$pkgname/app/main.py" "$pkgdir/opt/$pkgname/main.py" install -Dm755 "$srcdir/$pkgname/app/main.py" "$pkgdir/opt/$pkgname/main.py"
install -Dm755 "$srcdir/$pkgname/app/orm.py" "$pkgdir/opt/$pkgname/orm.py" install -Dm755 "$srcdir/$pkgname/app/orm.py" "$pkgdir/opt/$pkgname/orm.py"
install -Dm755 "$srcdir/$pkgname/app/util.py" "$pkgdir/opt/$pkgname/util.py" install -Dm755 "$srcdir/$pkgname/app/util.py" "$pkgdir/opt/$pkgname/util.py"
# copy static asset files
install -Dm755 "$srcdir/$pkgname/app/static/assets/css/bootstrap.min.css" "$pkgdir/opt/$pkgname/static/assets/css/bootstrap.min.css"
install -Dm755 "$srcdir/$pkgname/app/static/assets/css/bootstrap-icons.min.css" "$pkgdir/opt/$pkgname/static/assets/css/bootstrap-icons.min.css"
install -Dm755 "$srcdir/$pkgname/app/static/assets/css/custom.css" "$pkgdir/opt/$pkgname/static/assets/css/custom.css"
install -Dm755 "$srcdir/$pkgname/app/static/assets/css/dashboard.css" "$pkgdir/opt/$pkgname/static/assets/css/dashboard.css"
install -Dm755 "$srcdir/$pkgname/app/static/assets/fonts/bootstrap-icons.woff" "$pkgdir/opt/$pkgname/static/assets/fonts/bootstrap-icons.woff"
install -Dm755 "$srcdir/$pkgname/app/static/assets/fonts/bootstrap-icons.woff2" "$pkgdir/opt/$pkgname/static/assets/fonts/bootstrap-icons.woff2"
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/android-chrome-192x192.png" "$pkgdir/opt/$pkgname/static/assets/img/favicons/android-chrome-192x192.png"
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/android-chrome-512x512.png" "$pkgdir/opt/$pkgname/static/assets/img/favicons/android-chrome-512x512.png"
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/apple-touch-icon.png" "$pkgdir/opt/$pkgname/static/assets/img/favicons/apple-touch-icon.png"
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/favicon.ico" "$pkgdir/opt/$pkgname/static/assets/img/favicons/favicon.ico"
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/favicon-16x16.png" "$pkgdir/opt/$pkgname/static/assets/img/favicons/favicon-16x16.png"
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/favicon-32x32.png" "$pkgdir/opt/$pkgname/static/assets/img/favicons/favicon-32x32.png"
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/manifest.json" "$pkgdir/opt/$pkgname/static/assets/img/favicons/manifest.json"
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/logo.png" "$pkgdir/opt/$pkgname/static/assets/img/logo.png"
install -Dm755 "$srcdir/$pkgname/app/static/assets/js/bootstrap.min.js" "$pkgdir/opt/$pkgname/static/assets/js/bootstrap.min.js"
install -Dm755 "$srcdir/$pkgname/app/static/assets/js/helper.js" "$pkgdir/opt/$pkgname/static/assets/js/helper.js"
install -Dm755 "$srcdir/$pkgname/app/static/assets/js/popper.min.js" "$pkgdir/opt/$pkgname/static/assets/js/popper.min.js"
install -Dm755 "$srcdir/$pkgname/app/templates/components/navbar.html" "$pkgdir/opt/$pkgname/templates/components/navbar.html"
install -Dm755 "$srcdir/$pkgname/app/templates/components/sidebar.html" "$pkgdir/opt/$pkgname/templates/components/sidebar.html"
install -Dm755 "$srcdir/$pkgname/app/templates/layouts/base.html" "$pkgdir/opt/$pkgname/templates/layouts/base.html"
install -Dm755 "$srcdir/$pkgname/app/templates/layouts/bootstrap.html" "$pkgdir/opt/$pkgname/templates/layouts/bootstrap.html"
install -Dm755 "$srcdir/$pkgname/app/templates/layouts/bootstrap-dashboard.html" "$pkgdir/opt/$pkgname/templates/layouts/bootstrap-dashboard.html"
install -Dm755 "$srcdir/$pkgname/app/templates/views/dashboard.html" "$pkgdir/opt/$pkgname/templates/views/dashboard.html"
install -Dm755 "$srcdir/$pkgname/app/templates/views/dashboard_leases.html" "$pkgdir/opt/$pkgname/templates/views/dashboard_leases.html"
install -Dm755 "$srcdir/$pkgname/app/templates/views/dashboard_origins.html" "$pkgdir/opt/$pkgname/templates/views/dashboard_origins.html"
install -Dm755 "$srcdir/$pkgname/app/templates/views/dashboard_readme.html" "$pkgdir/opt/$pkgname/templates/views/dashboard_readme.html"
install -Dm755 "$srcdir/$pkgname/app/templates/views/index.html" "$pkgdir/opt/$pkgname/templates/views/index.html"
# copy service files
install -Dm644 "$srcdir/$pkgname.default" "$pkgdir/etc/default/$pkgname" install -Dm644 "$srcdir/$pkgname.default" "$pkgdir/etc/default/$pkgname"
install -Dm644 "$srcdir/$pkgname.service" "$pkgdir/usr/lib/systemd/system/$pkgname.service" install -Dm644 "$srcdir/$pkgname.service" "$pkgdir/usr/lib/systemd/system/$pkgname.service"
install -Dm644 "$srcdir/$pkgname.tmpfiles" "$pkgdir/usr/lib/tmpfiles.d/$pkgname.conf" install -Dm644 "$srcdir/$pkgname.tmpfiles" "$pkgdir/usr/lib/tmpfiles.d/$pkgname.conf"

View File

@@ -1,9 +1,7 @@
version: "2"
plugins: plugins:
bandit: bandit:
enabled: true enabled: true
sonar-python: sonar-python:
enabled: true enabled: true
config: pylint:
tests_patterns: enabled: true
- test/**

View File

@@ -8,9 +8,6 @@ include:
cache: cache:
key: one-key-to-rule-them-all key: one-key-to-rule-them-all
variables:
DOCKER_BUILDX_PLATFORM: "linux/amd64,linux/arm64"
build:docker: build:docker:
image: docker:dind image: docker:dind
interruptible: true interruptible: true
@@ -29,7 +26,7 @@ build:docker:
script: script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- IMAGE=$CI_REGISTRY/$CI_PROJECT_PATH/$CI_COMMIT_REF_NAME:$CI_COMMIT_SHA - IMAGE=$CI_REGISTRY/$CI_PROJECT_PATH/$CI_COMMIT_REF_NAME:$CI_COMMIT_SHA
- docker buildx build --progress=plain --platform $DOCKER_BUILDX_PLATFORM --build-arg VERSION=$CI_COMMIT_REF_NAME --build-arg COMMIT=$CI_COMMIT_SHA --tag $IMAGE --push . - docker buildx build --progress=plain --platform linux/amd64,linux/arm64 --build-arg VERSION=$CI_COMMIT_REF_NAME --build-arg COMMIT=$CI_COMMIT_SHA --tag $IMAGE --push .
- docker buildx imagetools inspect $IMAGE - docker buildx imagetools inspect $IMAGE
- echo "CS_IMAGE=$IMAGE" > container_scanning.env - echo "CS_IMAGE=$IMAGE" > container_scanning.env
artifacts: artifacts:
@@ -127,32 +124,16 @@ build:pacman:
- "*.pkg.tar.zst" - "*.pkg.tar.zst"
test: test:
image: python:3.12-slim-bookworm image: python:3.11-slim-bullseye
stage: test stage: test
interruptible: true
rules: rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_COMMIT_BRANCH
- if: $CI_COMMIT_TAG - if: $CI_COMMIT_TAG
- if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
changes:
- app/**/*
- test/**/*
variables: variables:
DATABASE: sqlite:///../app/db.sqlite DATABASE: sqlite:///../app/db.sqlite
parallel:
matrix:
- REQUIREMENTS:
- 'requirements.txt'
# - '.DEBIAN/requirements-bookworm-12.txt'
# - '.DEBIAN/requirements-ubuntu-24.04.txt'
# - '.DEBIAN/requirements-ubuntu-24.10.txt'
before_script: before_script:
- apt-get update && apt-get install -y python3-dev python3-pip python3-venv gcc - pip install -r requirements.txt
- python3 -m venv venv
- source venv/bin/activate
- pip install --upgrade pip
- pip install -r $REQUIREMENTS
- pip install pytest httpx - pip install pytest httpx
- mkdir -p app/cert - mkdir -p app/cert
- openssl genrsa -out app/cert/instance.private.pem 2048 - openssl genrsa -out app/cert/instance.private.pem 2048
@@ -165,7 +146,7 @@ test:
dotenv: version.env dotenv: version.env
junit: ['**/report.xml'] junit: ['**/report.xml']
.test:apt: .test:linux:
stage: test stage: test
rules: rules:
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
@@ -204,17 +185,15 @@ test:
- apt-get purge -qq -y fastapi-dls - apt-get purge -qq -y fastapi-dls
- apt-get autoremove -qq -y && apt-get clean -qq - apt-get autoremove -qq -y && apt-get clean -qq
test:apt: test:debian:
extends: .test:apt extends: .test:linux
image: $IMAGE image: debian:bookworm-slim
parallel:
matrix:
- IMAGE:
- debian:bookworm-slim # EOL: June 06, 2026
- ubuntu:24.04 # EOL: April 2036
- ubuntu:24.10
test:pacman:archlinux: test:ubuntu:
extends: .test:linux
image: ubuntu:22.10
test:archlinux:
image: archlinux:base image: archlinux:base
rules: rules:
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
@@ -230,13 +209,10 @@ test:pacman:archlinux:
- pacman -U --noconfirm *.pkg.tar.zst - pacman -U --noconfirm *.pkg.tar.zst
code_quality: code_quality:
variables:
SOURCE_CODE: app
rules: rules:
- if: $CODE_QUALITY_DISABLED - if: $CODE_QUALITY_DISABLED
when: never when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
secret_detection: secret_detection:
rules: rules:
@@ -251,25 +227,12 @@ semgrep-sast:
- if: $SAST_DISABLED - if: $SAST_DISABLED
when: never when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
test_coverage: test_coverage:
# extends: test extends: test
image: python:3.12-slim-bookworm
allow_failure: true allow_failure: true
stage: test
rules: rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_PIPELINE_SOURCE == "merge_request_event"
variables:
DATABASE: sqlite:///../app/db.sqlite
before_script:
- apt-get update && apt-get install -y python3-dev gcc
- pip install -r requirements.txt
- pip install pytest httpx
- mkdir -p app/cert
- openssl genrsa -out app/cert/instance.private.pem 2048
- openssl rsa -in app/cert/instance.private.pem -outform PEM -pubout -out app/cert/instance.public.pem
- cd test
script: script:
- pip install pytest pytest-cov - pip install pytest pytest-cov
- coverage run -m pytest main.py - coverage run -m pytest main.py
@@ -294,7 +257,6 @@ gemnasium-python-dependency_scanning:
- if: $DEPENDENCY_SCANNING_DISABLED - if: $DEPENDENCY_SCANNING_DISABLED
when: never when: never
- if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
.deploy: .deploy:
rules: rules:
@@ -302,24 +264,24 @@ gemnasium-python-dependency_scanning:
deploy:docker: deploy:docker:
extends: .deploy extends: .deploy
image: docker:dind
stage: deploy stage: deploy
tags: [ docker ]
before_script: before_script:
- echo "Building docker image for commit $CI_COMMIT_SHA with version $CI_COMMIT_REF_NAME" - echo "Building docker image for commit $CI_COMMIT_SHA with version $CI_COMMIT_REF_NAME"
- docker buildx inspect
- docker buildx create --use
script: script:
- echo "========== GitLab-Registry ==========" - echo "========== GitLab-Registry =========="
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- IMAGE=$CI_REGISTRY/$CI_PROJECT_PATH - IMAGE=$CI_REGISTRY/$CI_PROJECT_PATH/$CI_COMMIT_REF_NAME
- docker buildx build --progress=plain --platform $DOCKER_BUILDX_PLATFORM --build-arg VERSION=$CI_COMMIT_REF_NAME --build-arg COMMIT=$CI_COMMIT_SHA --tag $IMAGE:$CI_COMMIT_REF_NAME --push . - docker build . --build-arg VERSION=$CI_COMMIT_REF_NAME --build-arg COMMIT=$CI_COMMIT_SHA --tag $IMAGE:$CI_COMMIT_REF_NAME
- docker buildx build --progress=plain --platform $DOCKER_BUILDX_PLATFORM --build-arg VERSION=$CI_COMMIT_REF_NAME --build-arg COMMIT=$CI_COMMIT_SHA --tag $IMAGE:latest --push . - docker build . --build-arg VERSION=$CI_COMMIT_REF_NAME --build-arg COMMIT=$CI_COMMIT_SHA --tag $IMAGE:latest
- docker push $IMAGE:$CI_COMMIT_REF_NAME
- docker push $IMAGE:latest
- echo "========== Docker-Hub ==========" - echo "========== Docker-Hub =========="
- docker login -u $PUBLIC_REGISTRY_USER -p $PUBLIC_REGISTRY_TOKEN - docker login -u $PUBLIC_REGISTRY_USER -p $PUBLIC_REGISTRY_TOKEN
- IMAGE=$PUBLIC_REGISTRY_USER/$CI_PROJECT_NAME - IMAGE=$PUBLIC_REGISTRY_USER/$CI_PROJECT_NAME
- docker buildx build --progress=plain --platform $DOCKER_BUILDX_PLATFORM --build-arg VERSION=$CI_COMMIT_REF_NAME --build-arg COMMIT=$CI_COMMIT_SHA --tag $IMAGE:$CI_COMMIT_REF_NAME --push . - docker build . --build-arg VERSION=$CI_COMMIT_REF_NAME --build-arg COMMIT=$CI_COMMIT_SHA --tag $IMAGE:$CI_COMMIT_REF_NAME
- docker buildx build --progress=plain --platform $DOCKER_BUILDX_PLATFORM --build-arg VERSION=$CI_COMMIT_REF_NAME --build-arg COMMIT=$CI_COMMIT_SHA --tag $IMAGE:latest --push . - docker build . --build-arg VERSION=$CI_COMMIT_REF_NAME --build-arg COMMIT=$CI_COMMIT_SHA --tag $IMAGE:latest
- docker push $IMAGE:$CI_COMMIT_REF_NAME
- docker push $IMAGE:latest
deploy:apt: deploy:apt:
# doc: https://git.collinwebdesigns.de/help/user/packages/debian_repository/index.md#install-a-package # doc: https://git.collinwebdesigns.de/help/user/packages/debian_repository/index.md#install-a-package

View File

@@ -1,4 +1,4 @@
FROM python:3.12-alpine FROM python:3.11-alpine
ARG VERSION ARG VERSION
ARG COMMIT="" ARG COMMIT=""
@@ -10,7 +10,7 @@ RUN apk update \
&& apk add --no-cache --virtual build-deps gcc g++ python3-dev musl-dev pkgconfig \ && apk add --no-cache --virtual build-deps gcc g++ python3-dev musl-dev pkgconfig \
&& apk add --no-cache curl postgresql postgresql-dev mariadb-dev sqlite-dev \ && apk add --no-cache curl postgresql postgresql-dev mariadb-dev sqlite-dev \
&& pip install --no-cache-dir --upgrade uvicorn \ && pip install --no-cache-dir --upgrade uvicorn \
&& pip install --no-cache-dir psycopg2==2.9.10 mysqlclient==2.2.7 pysqlite3==0.5.4 \ && pip install --no-cache-dir psycopg2==2.9.6 mysqlclient==2.2.0 pysqlite3==0.5.1 \
&& pip install --no-cache-dir -r /tmp/requirements.txt \ && pip install --no-cache-dir -r /tmp/requirements.txt \
&& apk del build-deps && apk del build-deps

174
README.md
View File

@@ -2,33 +2,22 @@
Minimal Delegated License Service (DLS). Minimal Delegated License Service (DLS).
Compatibility tested with official NLS 2.0.1, 2.1.0, 3.1.0, 3.3.1, 3.4.0. For Driver compatibility Compatibility tested with official NLS 2.0.1, 2.1.0, 3.1.0.
see [compatibility matrix](#vgpu-software-compatibility-matrix).
This service can be used without internet connection. This service can be used without internet connection.
Only the clients need a connection to this service on configured port. Only the clients need a connection to this service on configured port.
**Official Links** **Official Links**
* https://git.collinwebdesigns.de/oscar.krause/fastapi-dls (Private Git) - https://git.collinwebdesigns.de/oscar.krause/fastapi-dls (Private Git)
* https://gitea.publichub.eu/oscar.krause/fastapi-dls (Public Git) - https://gitea.publichub.eu/oscar.krause/fastapi-dls (Public Git)
* https://hub.docker.com/r/collinwebdesigns/fastapi-dls (Docker-Hub `collinwebdesigns/fastapi-dls:latest`) - https://hub.docker.com/r/collinwebdesigns/fastapi-dls (Docker-Hub `collinwebdesigns/fastapi-dls:latest`)
*All other repositories are forks! (which is no bad - just for information and bug reports)* *All other repositories are forks! (which is no bad - just for information and bug reports)*
[Releases & Release Notes](https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/releases)
**Further Reading**
* [NVIDIA vGPU Guide](https://gitlab.com/polloloco/vgpu-proxmox) - This document serves as a guide to install NVIDIA vGPU host drivers on the latest Proxmox VE version
* [vgpu_unlock](https://github.com/DualCoder/vgpu_unlock) - Unlock vGPU functionality for consumer-grade Nvidia GPUs.
* [vGPU_Unlock Wiki](https://docs.google.com/document/d/1pzrWJ9h-zANCtyqRgS7Vzla0Y8Ea2-5z2HEi4X75d2Q) - Guide for `vgpu_unlock`
* [Proxmox 8 vGPU in VMs and LXC Containers](https://medium.com/@dionisievldulrincz/proxmox-8-vgpu-in-vms-and-lxc-containers-4146400207a3) - Install *Merged Drivers* for using in Proxmox VMs and LXCs
* [Proxmox All-In-One Installer Script](https://wvthoog.nl/proxmox-vgpu-v3/) - Also known as `proxmox-installer.sh`
--- ---
[TOC] [[_TOC_]]
# Setup (Service) # Setup (Service)
@@ -38,21 +27,18 @@ Only the clients need a connection to this service on configured port.
- 4gb hdd - 4gb hdd
- *maybe IPv6 must be disabled* - *maybe IPv6 must be disabled*
Tested with Ubuntu 22.10 (EOL!) (from Proxmox templates), actually its consuming 100mb ram and 750mb hdd. Tested with Ubuntu 22.10 (from Proxmox templates), actually its consuming 100mb ram and 750mb hdd.
**Prepare your system** **Prepare your system**
- Make sure your timezone is set correct on you fastapi-dls server and your client - Make sure your timezone is set correct on you fastapi-dls server and your client
This guide does not show how to install vGPU host drivers! Look at the official documentation packed with the driver
releases.
## Docker ## Docker
Docker-Images are available here for Intel (x86), AMD (amd64) and ARM (arm64): Docker-Images are available here:
- [Docker-Hub](https://hub.docker.com/repository/docker/collinwebdesigns/fastapi-dls): `collinwebdesigns/fastapi-dls:latest` - [Docker-Hub](https://hub.docker.com/repository/docker/collinwebdesigns/fastapi-dls): `collinwebdesigns/fastapi-dls:latest`
- [GitLab-Registry](https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/container_registry): `registry.git.collinwebdesigns.de/oscar.krause/fastapi-dls:latest` - [GitLab-Registry](https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/container_registry): `registry.git.collinwebdesigns.de/oscar.krause/fastapi-dls/main:latest`
The images include database drivers for `postgres`, `mariadb` and `sqlite`. The images include database drivers for `postgres`, `mariadb` and `sqlite`.
@@ -116,10 +102,9 @@ volumes:
dls-db: dls-db:
``` ```
## Debian / Ubuntu / macOS (manual method using `git clone` and python virtual environment) ## Debian/Ubuntu (manual method using `git clone` and python virtual environment)
Tested on `Debian 11 (bullseye)`, `Debian 12 (bookworm)` and `macOS Ventura (13.6)`, Ubuntu may also work. Tested on `Debian 11 (bullseye)`, Ubuntu may also work.
**Please note that setup on macOS differs from Debian based systems.**
**Make sure you are logged in as root.** **Make sure you are logged in as root.**
@@ -323,7 +308,7 @@ EOF
Now you have to run `systemctl daemon-reload`. After that you can start service Now you have to run `systemctl daemon-reload`. After that you can start service
with `systemctl start fastapi-dls.service` and enable autostart with `systemctl enable fastapi-dls.service`. with `systemctl start fastapi-dls.service` and enable autostart with `systemctl enable fastapi-dls.service`.
## Debian / Ubuntu (using `dpkg` / `apt`) ## Debian/Ubuntu (using `dpkg`)
Packages are available here: Packages are available here:
@@ -331,12 +316,8 @@ Packages are available here:
Successful tested with: Successful tested with:
- **Debian 12 (Bookworm)** (EOL: June 06, 2026) - Debian 12 (Bookworm)
- *Ubuntu 22.10 (Kinetic Kudu)* (EOL: July 20, 2023) - Ubuntu 22.10 (Kinetic Kudu)
- *Ubuntu 23.04 (Lunar Lobster)* (EOL: January 2024)
- *Ubuntu 23.10 (Mantic Minotaur)* (EOL: July 2024)
- **Ubuntu 24.04 (Noble Numbat)** (EOL: April 2036)
- *Ubuntu 24.10 (Oracular Oriole)* (EOL: tba.)
Not working with: Not working with:
@@ -394,10 +375,6 @@ Now you have to edit `/etc/default/fastapi-dls` as needed.
Continue [here](#unraid-guest) for docker guest setup. Continue [here](#unraid-guest) for docker guest setup.
## NixOS
Tanks to [@mrzenc](https://github.com/mrzenc) for [fastapi-dls-nixos](https://github.com/mrzenc/fastapi-dls-nixos).
## Let's Encrypt Certificate (optional) ## Let's Encrypt Certificate (optional)
If you're using installation via docker, you can use `traefik`. Please refer to their documentation. If you're using installation via docker, you can use `traefik`. Please refer to their documentation.
@@ -416,36 +393,46 @@ After first success you have to replace `--issue` with `--renew`.
# Configuration # Configuration
| Variable | Default | Usage | | Variable | Default | Usage |
|--------------------------|----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------| |------------------------|----------------------------------------|------------------------------------------------------------------------------------------------------|
| `DEBUG` | `false` | Toggles `fastapi` debug mode | | `DEBUG` | `false` | Toggles `fastapi` debug mode |
| `DLS_URL` | `localhost` | Used in client-token to tell guest driver where dls instance is reachable | | `DLS_URL` | `localhost` | Used in client-token to tell guest driver where dls instance is reachable |
| `DLS_PORT` | `443` | Used in client-token to tell guest driver where dls instance is reachable | | `DLS_PORT` | `443` | Used in client-token to tell guest driver where dls instance is reachable |
| `TOKEN_EXPIRE_DAYS` | `1` | Client auth-token validity (used for authenticate client against api, **not `.tok` file!**) | | `TOKEN_EXPIRE_DAYS` | `1` | Client auth-token validity (used for authenticate client against api, **not `.tok` file!**) |
| `LEASE_EXPIRE_DAYS` | `90` | Lease time in days | | `LEASE_EXPIRE_DAYS` | `90` | Lease time in days |
| `LEASE_RENEWAL_PERIOD` | `0.15` | The percentage of the lease period that must elapse before a licensed client can renew a license \*1 | | `LEASE_RENEWAL_PERIOD` | `0.15` | The percentage of the lease period that must elapse before a licensed client can renew a license \*1 |
| `DATABASE` | `sqlite:///db.sqlite` | See [official SQLAlchemy docs](https://docs.sqlalchemy.org/en/14/core/engines.html) | | `DATABASE` | `sqlite:///db.sqlite` | See [official SQLAlchemy docs](https://docs.sqlalchemy.org/en/14/core/engines.html) |
| `CORS_ORIGINS` | `https://{DLS_URL}` | Sets `Access-Control-Allow-Origin` header (comma separated string) \*2 | | `CORS_ORIGINS` | `https://{DLS_URL}` | Sets `Access-Control-Allow-Origin` header (comma separated string) \*2 |
| `SITE_KEY_XID` | `00000000-0000-0000-0000-000000000000` | Site identification uuid | | `SITE_KEY_XID` | `00000000-0000-0000-0000-000000000000` | Site identification uuid |
| `INSTANCE_REF` | `10000000-0000-0000-0000-000000000001` | Instance identification uuid | | `INSTANCE_REF` | `10000000-0000-0000-0000-000000000001` | Instance identification uuid |
| `ALLOTMENT_REF` | `20000000-0000-0000-0000-000000000001` | Allotment identification uuid | | `ALLOTMENT_REF` | `20000000-0000-0000-0000-000000000001` | Allotment identification uuid |
| `INSTANCE_KEY_RSA` | `<app-dir>/cert/instance.private.pem` | Site-wide private RSA key for singing JWTs \*3 | | `INSTANCE_KEY_RSA` | `<app-dir>/cert/instance.private.pem` | Site-wide private RSA key for singing JWTs \*3 |
| `INSTANCE_KEY_PUB` | `<app-dir>/cert/instance.public.pem` | Site-wide public key \*3 | | `INSTANCE_KEY_PUB` | `<app-dir>/cert/instance.public.pem` | Site-wide public key \*3 |
\*1 For example, if the lease period is one day and the renewal period is 20%, the client attempts to renew its license \*1 For example, if the lease period is one day and the renewal period is 20%, the client attempts to renew its license
every 4.8 hours. If network connectivity is lost, the loss of connectivity is detected during license renewal and the every 4.8 hours. If network connectivity is lost, the loss of connectivity is detected during license renewal and the
client has 19.2 hours in which to re-establish connectivity before its license expires. client has 19.2 hours in which to re-establish connectivity before its license expires.
\*2 Always use `https`, since guest-drivers only support secure connections! \*3 Always use `https`, since guest-drivers only support secure connections!
\*3 If you recreate your instance keys you need to **recreate client-token for each guest**! \*4 If you recreate instance keys you need to **recreate client-token for each guest**!
# Setup (Client) # Setup (Client)
**The token file has to be copied! It's not enough to C&P file contents, because there can be special characters.** **The token file has to be copied! It's not enough to C&P file contents, because there can be special characters.**
This guide does not show how to install vGPU guest drivers! Look at the official documentation packed with the driver Successfully tested with this package versions:
releases.
| vGPU Suftware | vGPU Manager | Linux Driver | Windows Driver | Release Date |
|---------------|--------------|--------------|----------------|---------------|
| `15.3` | `525.125.03` | `525.125.06` | `529.11` | June 2023 |
| `15.2` | `525.105.14` | `525.105.17` | `528.89` | March 2023 |
| `15.1` | `525.85.07` | `525.85.05` | `528.24` | January 2023 |
| `15.0` | `525.60.12` | `525.60.13` | `527.41` | December 2022 |
| `14.4` | `510.108.03` | `510.108.03` | `514.08` | December 2022 |
| `14.3` | `510.108.03` | `510.108.03` | `513.91` | November 2022 |
- https://docs.nvidia.com/grid/index.html
## Linux ## Linux
@@ -521,32 +508,33 @@ Done. For more information check [troubleshoot section](#troubleshoot).
8. Set schedule to `At First Array Start Only` 8. Set schedule to `At First Array Start Only`
9. Click on Apply 9. Click on Apply
# API Endpoints
# Endpoints
<details> <details>
<summary>show</summary> <summary>show</summary>
**`GET /`** ### `GET /`
Redirect to `/-/readme`. Redirect to `/-/readme`.
**`GET /-/health`** ### `GET /-/health`
Status endpoint, used for *healthcheck*. Status endpoint, used for *healthcheck*.
**`GET /-/config`** ### `GET /-/config`
Shows current runtime environment variables and their values. Shows current runtime environment variables and their values.
**`GET /-/readme`** ### `GET /-/readme`
HTML rendered README.md. HTML rendered README.md.
**`GET /-/manage`** ### `GET /-/manage`
Shows a very basic UI to delete origins or leases. Shows a very basic UI to delete origins or leases.
**`GET /-/origins?leases=false`** ### `GET /-/origins?leases=false`
List registered origins. List registered origins.
@@ -554,11 +542,11 @@ List registered origins.
|-----------------|---------|--------------------------------------| |-----------------|---------|--------------------------------------|
| `leases` | `false` | Include referenced leases per origin | | `leases` | `false` | Include referenced leases per origin |
**`DELETE /-/origins`** ### `DELETE /-/origins`
Deletes all origins and their leases. Deletes all origins and their leases.
**`GET /-/leases?origin=false`** ### `GET /-/leases?origin=false`
List current leases. List current leases.
@@ -566,20 +554,20 @@ List current leases.
|-----------------|---------|-------------------------------------| |-----------------|---------|-------------------------------------|
| `origin` | `false` | Include referenced origin per lease | | `origin` | `false` | Include referenced origin per lease |
**`DELETE /-/lease/{lease_ref}`** ### `DELETE /-/lease/{lease_ref}`
Deletes an lease. Deletes an lease.
**`GET /-/client-token`** ### `GET /-/client-token`
Generate client token, (see [installation](#installation)). Generate client token, (see [installation](#installation)).
**Others** ### Others
There are many other internal api endpoints for handling authentication and lease process. There are many other internal api endpoints for handling authentication and lease process.
</details> </details>
# Troubleshoot / Debug # Troubleshoot
**Please make sure that fastapi-dls and your guests are on the same timezone!** **Please make sure that fastapi-dls and your guests are on the same timezone!**
@@ -601,9 +589,9 @@ Logs are available in `C:\Users\Public\Documents\Nvidia\LoggingLog.NVDisplay.Con
## Linux ## Linux
### Invalid HTTP request ### `uvicorn.error:Invalid HTTP request received.`
This error message: `uvicorn.error:Invalid HTTP request received.` can be ignored. This message can be ignored.
- Ref. https://github.com/encode/uvicorn/issues/441 - Ref. https://github.com/encode/uvicorn/issues/441
@@ -729,50 +717,12 @@ The error message can safely be ignored (since we have no license limitation :P)
</details> </details>
# vGPU Software Compatibility Matrix
Successfully tested with this package versions.
| vGPU Suftware | Driver Branch | Linux vGPU Manager | Linux Driver | Windows Driver | Release Date | EOL Date |
|:-------------:|:-------------:|--------------------|--------------|----------------|--------------:|--------------:|
| `17.5` | R550 | `550.144.02` | `550.144.03` | `553.62` | January 2025 | June 2025 |
| `17.4` | R550 | `550.127.06` | `550.127.05` | `553.24` | October 2024 | |
| `17.3` | R550 | `550.90.05` | `550.90.07` | `552.74` | July 2024 | |
| `17.2` | R550 | `550.90.05` | `550.90.07` | `552.55` | June 2024 | |
| `17.1` | R550 | `550.54.16` | `550.54.15` | `551.78` | March 2024 | |
| `17.0` | R550 | `550.54.10` | `550.54.14` | `551.61` | February 2024 | |
| `16.9` | R535 | `535.230.02` | `535.216.01` | `539.19` | October 2024 | July 2026 |
| `16.8` | R535 | `535.216.01` | `535.216.01` | `538.95` | October 2024 | |
| `16.7` | R535 | `535.183.04` | `535.183.06` | `538.78` | July 2024 | |
| `16.6` | R535 | `535.183.04` | `535.183.01` | `538.67` | June 2024 | |
| `16.5` | R535 | `535.161.05` | `535.161.08` | `538.46` | February 2024 | |
| `16.4` | R535 | `535.161.05` | `535.161.07` | `538.33` | February 2024 | |
| `16.3` | R535 | `535.154.02` | `535.154.05` | `538.15` | January 2024 | |
| `16.2` | R535 | `535.129.03` | `535.129.03` | `537.70` | October 2023 | |
| `16.1` | R535 | `535.104.06` | `535.104.05` | `537.13` | August 2023 | |
| `16.0` | R535 | `535.54.06` | `535.54.03` | `536.22` | July 2023 | |
| `15.4` | R525 | `525.147.01` | `525.147.05` | `529.19` | June 2023 | December 2023 |
| `14.4` | R510 | `510.108.03` | `510.108.03` | `514.08` | December 2022 | February 2023 |
- https://docs.nvidia.com/grid/index.html
- https://docs.nvidia.com/grid/gpus-supported-by-vgpu.html
*To get the latest drivers, visit Nvidia or search in Discord-Channel `GPU Unlocking` (Server-ID: `829786927829745685`)
on channel `licensing`
# Credits # Credits
Thanks to vGPU community and all who uses this project and report bugs. Thanks to vGPU community and all who uses this project and report bugs.
Special thanks to: Special thanks to
- @samicrusader who created build file for **ArchLinux** - @samicrusader who created build file for ArchLinux
- @cyrus who wrote the section for **openSUSE** - @cyrus who wrote the section for openSUSE
- @midi who wrote the section for **unRAID** - @midi who wrote the section for unRAID
- @polloloco who wrote the *[NVIDIA vGPU Guide](https://gitlab.com/polloloco/vgpu-proxmox)*
- @DualCoder who creates the `vgpu_unlock` functionality [vgpu_unlock](https://github.com/DualCoder/vgpu_unlock)
- Krutav Shah who wrote the [vGPU_Unlock Wiki](https://docs.google.com/document/d/1pzrWJ9h-zANCtyqRgS7Vzla0Y8Ea2-5z2HEi4X75d2Q/)
- Wim van 't Hoog for the [Proxmox All-In-One Installer Script](https://wvthoog.nl/proxmox-vgpu-v3/)
- @mrzenc who wrote [fastapi-dls-nixos](https://github.com/mrzenc/fastapi-dls-nixos)
And thanks to all people who contributed to all these libraries!

View File

@@ -1,42 +1,44 @@
import logging import logging
from base64 import b64encode as b64enc from base64 import b64encode as b64enc
from calendar import timegm
from contextlib import asynccontextmanager
from datetime import datetime, timedelta, UTC
from hashlib import sha256 from hashlib import sha256
from json import loads as json_loads
from os import getenv as env
from os.path import join, dirname
from uuid import uuid4 from uuid import uuid4
from os.path import join, dirname
from os import getenv as env
from dateutil.relativedelta import relativedelta
from dotenv import load_dotenv from dotenv import load_dotenv
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.requests import Request from fastapi.requests import Request
from json import loads as json_loads
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from calendar import timegm
from jose import jws, jwk, jwt, JWTError from jose import jws, jwk, jwt, JWTError
from jose.constants import ALGORITHMS from jose.constants import ALGORITHMS
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from starlette.middleware.cors import CORSMiddleware from starlette.middleware.cors import CORSMiddleware
from starlette.responses import StreamingResponse, JSONResponse as JSONr, HTMLResponse as HTMLr, Response, RedirectResponse from starlette.responses import StreamingResponse, JSONResponse as JSONr, HTMLResponse as HTMLr, Response, RedirectResponse
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from starlette.staticfiles import StaticFiles
from starlette.templating import Jinja2Templates
from orm import Origin, Lease, init as db_init, migrate
from util import load_key, load_file from util import load_key, load_file
from orm import Origin, Lease, init as db_init, migrate
# Load variables
load_dotenv('../version.env') load_dotenv('../version.env')
# Get current timezone
TZ = datetime.now().astimezone().tzinfo TZ = datetime.now().astimezone().tzinfo
# Load basic variables
VERSION, COMMIT, DEBUG = env('VERSION', 'unknown'), env('COMMIT', 'unknown'), bool(env('DEBUG', False)) VERSION, COMMIT, DEBUG = env('VERSION', 'unknown'), env('COMMIT', 'unknown'), bool(env('DEBUG', False))
# Database connection config = dict(openapi_url=None, docs_url=None, redoc_url=None) # dict(openapi_url='/-/openapi.json', docs_url='/-/docs', redoc_url='/-/redoc')
app = FastAPI(title='FastAPI-DLS', description='Minimal Delegated License Service (DLS).', version=VERSION, **config)
app.mount('/static', StaticFiles(directory=join(dirname(__file__), 'static'), html=True), name='static'),
templates = Jinja2Templates(directory=join(dirname(__file__), 'templates'))
db = create_engine(str(env('DATABASE', 'sqlite:///db.sqlite'))) db = create_engine(str(env('DATABASE', 'sqlite:///db.sqlite')))
db_init(db), migrate(db) db_init(db), migrate(db)
# Load DLS variables (all prefixed with "INSTANCE_*" is used as "SERVICE_INSTANCE_*" or "SI_*" in official dls service) # everything prefixed with "INSTANCE_*" is used as "SERVICE_INSTANCE_*" or "SI_*" in official dls service
DLS_URL = str(env('DLS_URL', 'localhost')) DLS_URL = str(env('DLS_URL', 'localhost'))
DLS_PORT = int(env('DLS_PORT', '443')) DLS_PORT = int(env('DLS_PORT', '443'))
SITE_KEY_XID = str(env('SITE_KEY_XID', '00000000-0000-0000-0000-000000000000')) SITE_KEY_XID = str(env('SITE_KEY_XID', '00000000-0000-0000-0000-000000000000'))
@@ -54,40 +56,6 @@ CORS_ORIGINS = str(env('CORS_ORIGINS', '')).split(',') if (env('CORS_ORIGINS'))
jwt_encode_key = jwk.construct(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256) jwt_encode_key = jwk.construct(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256)
jwt_decode_key = jwk.construct(INSTANCE_KEY_PUB.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256) jwt_decode_key = jwk.construct(INSTANCE_KEY_PUB.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256)
# Logging
LOG_LEVEL = logging.DEBUG if DEBUG else logging.INFO
logging.basicConfig(format='[{levelname:^7}] [{module:^15}] {message}', style='{')
logger = logging.getLogger(__name__)
logger.setLevel(LOG_LEVEL)
logging.getLogger('util').setLevel(LOG_LEVEL)
logging.getLogger('NV').setLevel(LOG_LEVEL)
# FastAPI
@asynccontextmanager
async def lifespan(_: FastAPI):
# on startup
logger.info(f'''
Using timezone: {str(TZ)}. Make sure this is correct and match your clients!
Your clients renew their license every {str(Lease.calculate_renewal(LEASE_RENEWAL_PERIOD, LEASE_RENEWAL_DELTA))}.
If the renewal fails, the license is {str(LEASE_RENEWAL_DELTA)} valid.
Your client-token file (.tok) is valid for {str(CLIENT_TOKEN_EXPIRE_DELTA)}.
''')
logger.info(f'Debug is {"enabled" if DEBUG else "disabled"}.')
yield
# on shutdown
logger.info(f'Shutting down ...')
config = dict(openapi_url=None, docs_url=None, redoc_url=None) # dict(openapi_url='/-/openapi.json', docs_url='/-/docs', redoc_url='/-/redoc')
app = FastAPI(title='FastAPI-DLS', description='Minimal Delegated License Service (DLS).', version=VERSION, lifespan=lifespan, **config)
app.debug = DEBUG app.debug = DEBUG
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
@@ -97,24 +65,25 @@ app.add_middleware(
allow_headers=['*'], allow_headers=['*'],
) )
logging.basicConfig()
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG if DEBUG else logging.INFO)
# Helper
def __get_token(request: Request) -> dict: def __get_token(request: Request) -> dict:
authorization_header = request.headers.get('authorization') authorization_header = request.headers.get('authorization')
token = authorization_header.split(' ')[1] token = authorization_header.split(' ')[1]
return jwt.decode(token=token, key=jwt_decode_key, algorithms=ALGORITHMS.RS256, options={'verify_aud': False}) return jwt.decode(token=token, key=jwt_decode_key, algorithms=ALGORITHMS.RS256, options={'verify_aud': False})
# Endpoints @app.get('/', summary='* Index')
@app.get('/', summary='Index')
async def index(): async def index():
return RedirectResponse('/-/readme') return RedirectResponse('/-/')
@app.get('/-/', summary='* Index') @app.get('/-/', summary='* Index')
async def _index(): async def _index(request: Request):
return RedirectResponse('/-/readme') return templates.TemplateResponse(name='views/index.html', context={'request': request, 'VERSION': VERSION})
@app.get('/-/health', summary='* Health') @app.get('/-/health', summary='* Health')
@@ -142,48 +111,32 @@ async def _config():
@app.get('/-/readme', summary='* Readme') @app.get('/-/readme', summary='* Readme')
async def _readme(): async def _readme(request: Request):
from markdown import markdown from markdown import markdown
content = load_file(join(dirname(__file__), '../README.md')).decode('utf-8') content = load_file('../README.md').decode('utf-8')
return HTMLr(markdown(text=content, extensions=['tables', 'fenced_code', 'md_in_html', 'nl2br', 'toc'])) markdown = markdown(text=content, extensions=['tables', 'fenced_code', 'md_in_html', 'nl2br', 'toc'])
context = {'request': request, 'markdown': markdown, 'VERSION': VERSION}
return templates.TemplateResponse(name='views/dashboard_readme.html', context=context)
@app.get('/-/manage', summary='* Management UI') @app.get('/-/manage', summary='* Management UI')
async def _manage(request: Request): async def _manage(request: Request):
response = ''' return templates.TemplateResponse(name='views/manage.html', context={'request': request, 'VERSION': VERSION})
<!DOCTYPE html>
<html>
<head>
<title>FastAPI-DLS Management</title>
</head>
<body>
<button onclick="deleteOrigins()">delete ALL origins and their leases</button>
<button onclick="deleteLease()">delete specific lease</button>
<script>
function deleteOrigins() {
const response = confirm('Are you sure you want to delete all origins and their leases?');
if (response) { @app.get('/-/dashboard', summary='* Dashboard')
var xhr = new XMLHttpRequest(); async def _dashboard(request: Request):
xhr.open("DELETE", '/-/origins', true); return templates.TemplateResponse(name='views/dashboard.html', context={'request': request, 'VERSION': VERSION})
xhr.send();
}
} @app.get('/-/dashboard/origins', summary='* Dashboard - Origins')
function deleteLease(lease_ref) { async def _dashboard_origins(request: Request):
if(lease_ref === undefined) return templates.TemplateResponse(name='views/dashboard_origins.html', context={'request': request, 'VERSION': VERSION})
lease_ref = window.prompt("Please enter 'lease_ref' which should be deleted");
if(lease_ref === null || lease_ref === "")
return @app.get('/-/dashboard/leases', summary='* Dashboard - Leases')
var xhr = new XMLHttpRequest(); async def _dashboard_origins(request: Request):
xhr.open("DELETE", `/-/lease/${lease_ref}`, true); return templates.TemplateResponse(name='views/dashboard_leases.html', context={'request': request, 'VERSION': VERSION})
xhr.send();
}
</script>
</body>
</html>
'''
return HTMLr(response)
@app.get('/-/origins', summary='* Origins') @app.get('/-/origins', summary='* Origins')
@@ -206,6 +159,13 @@ async def _origins_delete(request: Request):
return Response(status_code=201) return Response(status_code=201)
@app.delete('/-/origins/{origin_ref}', summary='* Origins')
async def _origins_delete_origin_ref(request: Request, origin_ref: str):
if Origin.delete(db, [origin_ref]) == 1:
return Response(status_code=201)
return JSONr(status_code=404, content={'status': 404, 'detail': 'lease not found'})
@app.get('/-/leases', summary='* Leases') @app.get('/-/leases', summary='* Leases')
async def _leases(request: Request, origin: bool = False): async def _leases(request: Request, origin: bool = False):
session = sessionmaker(bind=db)() session = sessionmaker(bind=db)()
@@ -238,7 +198,7 @@ async def _lease_delete(request: Request, lease_ref: str):
# venv/lib/python3.9/site-packages/nls_core_service_instance/service_instance_token_manager.py # venv/lib/python3.9/site-packages/nls_core_service_instance/service_instance_token_manager.py
@app.get('/-/client-token', summary='* Client-Token', description='creates a new messenger token for this service instance') @app.get('/-/client-token', summary='* Client-Token', description='creates a new messenger token for this service instance')
async def _client_token(): async def _client_token():
cur_time = datetime.now(UTC) cur_time = datetime.utcnow()
exp_time = cur_time + CLIENT_TOKEN_EXPIRE_DELTA exp_time = cur_time + CLIENT_TOKEN_EXPIRE_DELTA
payload = { payload = {
@@ -284,7 +244,7 @@ async def _client_token():
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py # venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py
@app.post('/auth/v1/origin', description='find or create an origin') @app.post('/auth/v1/origin', description='find or create an origin')
async def auth_v1_origin(request: Request): async def auth_v1_origin(request: Request):
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.now(UTC) j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.utcnow()
origin_ref = j.get('candidate_origin_ref') origin_ref = j.get('candidate_origin_ref')
logging.info(f'> [ origin ]: {origin_ref}: {j}') logging.info(f'> [ origin ]: {origin_ref}: {j}')
@@ -314,7 +274,7 @@ async def auth_v1_origin(request: Request):
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py # venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py
@app.post('/auth/v1/origin/update', description='update an origin evidence') @app.post('/auth/v1/origin/update', description='update an origin evidence')
async def auth_v1_origin_update(request: Request): async def auth_v1_origin_update(request: Request):
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.now(UTC) j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.utcnow()
origin_ref = j.get('origin_ref') origin_ref = j.get('origin_ref')
logging.info(f'> [ update ]: {origin_ref}: {j}') logging.info(f'> [ update ]: {origin_ref}: {j}')
@@ -341,7 +301,7 @@ async def auth_v1_origin_update(request: Request):
# venv/lib/python3.9/site-packages/nls_core_auth/auth.py - CodeResponse # venv/lib/python3.9/site-packages/nls_core_auth/auth.py - CodeResponse
@app.post('/auth/v1/code', description='get an authorization code') @app.post('/auth/v1/code', description='get an authorization code')
async def auth_v1_code(request: Request): async def auth_v1_code(request: Request):
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.now(UTC) j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.utcnow()
origin_ref = j.get('origin_ref') origin_ref = j.get('origin_ref')
logging.info(f'> [ code ]: {origin_ref}: {j}') logging.info(f'> [ code ]: {origin_ref}: {j}')
@@ -373,10 +333,10 @@ async def auth_v1_code(request: Request):
# venv/lib/python3.9/site-packages/nls_core_auth/auth.py - TokenResponse # venv/lib/python3.9/site-packages/nls_core_auth/auth.py - TokenResponse
@app.post('/auth/v1/token', description='exchange auth code and verifier for token') @app.post('/auth/v1/token', description='exchange auth code and verifier for token')
async def auth_v1_token(request: Request): async def auth_v1_token(request: Request):
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.now(UTC) j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.utcnow()
try: try:
payload = jwt.decode(token=j.get('auth_code'), key=jwt_decode_key, algorithms=ALGORITHMS.RS256) payload = jwt.decode(token=j.get('auth_code'), key=jwt_decode_key)
except JWTError as e: except JWTError as e:
return JSONr(status_code=400, content={'status': 400, 'title': 'invalid token', 'detail': str(e)}) return JSONr(status_code=400, content={'status': 400, 'title': 'invalid token', 'detail': str(e)})
@@ -415,7 +375,7 @@ async def auth_v1_token(request: Request):
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py # venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py
@app.post('/leasing/v1/lessor', description='request multiple leases (borrow) for current origin') @app.post('/leasing/v1/lessor', description='request multiple leases (borrow) for current origin')
async def leasing_v1_lessor(request: Request): async def leasing_v1_lessor(request: Request):
j, token, cur_time = json_loads((await request.body()).decode('utf-8')), __get_token(request), datetime.now(UTC) j, token, cur_time = json_loads((await request.body()).decode('utf-8')), __get_token(request), datetime.utcnow()
try: try:
token = __get_token(request) token = __get_token(request)
@@ -463,7 +423,7 @@ async def leasing_v1_lessor(request: Request):
# venv/lib/python3.9/site-packages/nls_dal_service_instance_dls/schema/service_instance/V1_0_21__product_mapping.sql # venv/lib/python3.9/site-packages/nls_dal_service_instance_dls/schema/service_instance/V1_0_21__product_mapping.sql
@app.get('/leasing/v1/lessor/leases', description='get active leases for current origin') @app.get('/leasing/v1/lessor/leases', description='get active leases for current origin')
async def leasing_v1_lessor_lease(request: Request): async def leasing_v1_lessor_lease(request: Request):
token, cur_time = __get_token(request), datetime.now(UTC) token, cur_time = __get_token(request), datetime.utcnow()
origin_ref = token.get('origin_ref') origin_ref = token.get('origin_ref')
@@ -483,7 +443,7 @@ async def leasing_v1_lessor_lease(request: Request):
# venv/lib/python3.9/site-packages/nls_core_lease/lease_single.py # venv/lib/python3.9/site-packages/nls_core_lease/lease_single.py
@app.put('/leasing/v1/lease/{lease_ref}', description='renew a lease') @app.put('/leasing/v1/lease/{lease_ref}', description='renew a lease')
async def leasing_v1_lease_renew(request: Request, lease_ref: str): async def leasing_v1_lease_renew(request: Request, lease_ref: str):
token, cur_time = __get_token(request), datetime.now(UTC) token, cur_time = __get_token(request), datetime.utcnow()
origin_ref = token.get('origin_ref') origin_ref = token.get('origin_ref')
logging.info(f'> [ renew ]: {origin_ref}: renew {lease_ref}') logging.info(f'> [ renew ]: {origin_ref}: renew {lease_ref}')
@@ -510,7 +470,7 @@ async def leasing_v1_lease_renew(request: Request, lease_ref: str):
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_single_controller.py # venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_single_controller.py
@app.delete('/leasing/v1/lease/{lease_ref}', description='release (return) a lease') @app.delete('/leasing/v1/lease/{lease_ref}', description='release (return) a lease')
async def leasing_v1_lease_delete(request: Request, lease_ref: str): async def leasing_v1_lease_delete(request: Request, lease_ref: str):
token, cur_time = __get_token(request), datetime.now(UTC) token, cur_time = __get_token(request), datetime.utcnow()
origin_ref = token.get('origin_ref') origin_ref = token.get('origin_ref')
logging.info(f'> [ return ]: {origin_ref}: return {lease_ref}') logging.info(f'> [ return ]: {origin_ref}: return {lease_ref}')
@@ -536,7 +496,7 @@ async def leasing_v1_lease_delete(request: Request, lease_ref: str):
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py # venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py
@app.delete('/leasing/v1/lessor/leases', description='release all leases') @app.delete('/leasing/v1/lessor/leases', description='release all leases')
async def leasing_v1_lessor_lease_remove(request: Request): async def leasing_v1_lessor_lease_remove(request: Request):
token, cur_time = __get_token(request), datetime.now(UTC) token, cur_time = __get_token(request), datetime.utcnow()
origin_ref = token.get('origin_ref') origin_ref = token.get('origin_ref')
@@ -556,7 +516,7 @@ async def leasing_v1_lessor_lease_remove(request: Request):
@app.post('/leasing/v1/lessor/shutdown', description='shutdown all leases') @app.post('/leasing/v1/lessor/shutdown', description='shutdown all leases')
async def leasing_v1_lessor_shutdown(request: Request): async def leasing_v1_lessor_shutdown(request: Request):
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.now(UTC) j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.utcnow()
token = j.get('token') token = j.get('token')
token = jwt.decode(token=token, key=jwt_decode_key, algorithms=ALGORITHMS.RS256, options={'verify_aud': False}) token = jwt.decode(token=token, key=jwt_decode_key, algorithms=ALGORITHMS.RS256, options={'verify_aud': False})
@@ -576,6 +536,18 @@ async def leasing_v1_lessor_shutdown(request: Request):
return JSONr(response) return JSONr(response)
@app.on_event('startup')
async def app_on_startup():
logger.info(f'''
Using timezone: {str(TZ)}. Make sure this is correct and match your clients!
Your clients renew their license every {str(Lease.calculate_renewal(LEASE_RENEWAL_PERIOD, LEASE_RENEWAL_DELTA))}.
If the renewal fails, the license is {str(LEASE_RENEWAL_DELTA)} valid.
Your client-token file (.tok) is valid for {str(CLIENT_TOKEN_EXPIRE_DELTA)}.
''')
if __name__ == '__main__': if __name__ == '__main__':
import uvicorn import uvicorn

View File

@@ -1,12 +1,10 @@
from datetime import datetime, timedelta, timezone, UTC from datetime import datetime, timedelta, timezone
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from sqlalchemy import Column, VARCHAR, CHAR, ForeignKey, DATETIME, update, and_, inspect, text from sqlalchemy import Column, VARCHAR, CHAR, ForeignKey, DATETIME, update, and_, inspect, text
from sqlalchemy.engine import Engine from sqlalchemy.engine import Engine
from sqlalchemy.orm import sessionmaker, declarative_base from sqlalchemy.orm import sessionmaker, declarative_base
from util import NV
Base = declarative_base() Base = declarative_base()
@@ -25,8 +23,6 @@ class Origin(Base):
return f'Origin(origin_ref={self.origin_ref}, hostname={self.hostname})' return f'Origin(origin_ref={self.origin_ref}, hostname={self.hostname})'
def serialize(self) -> dict: def serialize(self) -> dict:
_ = NV().find(self.guest_driver_version)
return { return {
'origin_ref': self.origin_ref, 'origin_ref': self.origin_ref,
# 'service_instance_xid': self.service_instance_xid, # 'service_instance_xid': self.service_instance_xid,
@@ -34,7 +30,6 @@ class Origin(Base):
'guest_driver_version': self.guest_driver_version, 'guest_driver_version': self.guest_driver_version,
'os_platform': self.os_platform, 'os_platform': self.os_platform,
'os_version': self.os_version, 'os_version': self.os_version,
'$driver': _ if _ is not None else None,
} }
@staticmethod @staticmethod
@@ -71,16 +66,6 @@ class Origin(Base):
session.close() session.close()
return deletions return deletions
@staticmethod
def delete_expired(engine: Engine) -> int:
session = sessionmaker(bind=engine)()
origins = session.query(Origin).join(Lease, Origin.origin_ref == Lease.origin_ref, isouter=True).filter(Lease.lease_ref.is_(None)).all()
origin_refs = [origin.origin_ref for origin in origins]
deletions = session.query(Origin).filter(Origin.origin_ref.in_(origin_refs)).delete()
session.commit()
session.close()
return deletions
class Lease(Base): class Lease(Base):
__tablename__ = "lease" __tablename__ = "lease"
@@ -178,7 +163,7 @@ class Lease(Base):
@staticmethod @staticmethod
def delete_expired(engine: Engine) -> int: def delete_expired(engine: Engine) -> int:
session = sessionmaker(bind=engine)() session = sessionmaker(bind=engine)()
deletions = session.query(Lease).filter(Lease.lease_expires <= datetime.now(UTC)).delete() deletions = session.query(Lease).filter(Lease.lease_expires <= datetime.utcnow()).delete()
session.commit() session.commit()
session.close() session.close()
return deletions return deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,56 @@
/*
Original: #76b900
Darken 1: #5DA000 (10%)
Darken 2: #438600 (20%)
Darken 3: #2A6D00 (30%)
Darken 4: #105300 (40%)
Darken 5: #003A00 (50%)
*/
.text-primary {
color: #76b900 !important;
}
.lead {
color: #105300 !important;
}
.navbar-green {
background-color: #76b900 !important;
}
.navbar-brand {
background-color: transparent;
color: #ffffff;
}
.navbar-brand:focus, .navbar-brand:hover {
color: #fcfcfc;
}
.btn-primary {
background-color: #76b900 !important;
border-color: #76b900 !important;
}
.btn-primary:focus, .btn-primary:hover {
background-color: #5DA000 !important;
border-color: #5DA000 !important;
}
code {
color: #105300 !important;
}
.sidebar .nav-link.active {
color: #76b900 !important;
}
.sidebar .nav-link:focus, .sidebar .nav-link:hover {
color: #105300 !important;
}
.navbar-nav .nav-item .nav-link {
color: white !important;
}

View File

@@ -0,0 +1,101 @@
body {
font-size: .875rem;
}
.feather {
width: 16px;
height: 16px;
vertical-align: text-bottom;
}
/*
* Sidebar
*/
.sidebar {
position: fixed;
top: 0;
/* rtl:raw:
right: 0;
*/
bottom: 0;
/* rtl:remove */
left: 0;
z-index: 100; /* Behind the navbar */
padding: 48px 0 0; /* Height of navbar */
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
}
@media (max-width: 767.98px) {
.sidebar {
top: 0;
}
}
.sidebar-sticky {
position: relative;
top: 0;
height: calc(100vh - 48px);
padding-top: .5rem;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
}
.sidebar .nav-link {
font-weight: 500;
color: #333;
}
.sidebar .nav-link .feather {
margin-right: 4px;
color: #727272;
}
.sidebar .nav-link.active {
color: #2470dc;
}
.sidebar .nav-link:hover .feather,
.sidebar .nav-link.active .feather {
color: inherit;
}
.sidebar-heading {
font-size: .75rem;
text-transform: uppercase;
}
/*
* Navbar
*/
.navbar-brand {
padding-top: .75rem;
padding-bottom: .75rem;
font-size: 1rem;
background-color: rgba(0, 0, 0, .25);
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
}
.navbar .navbar-toggler {
top: .25rem;
right: 1rem;
}
.navbar .form-control {
padding: .75rem 1rem;
border-width: 0;
border-radius: 0;
}
.form-control-dark {
color: #fff;
background-color: rgba(255, 255, 255, .1);
border-color: rgba(255, 255, 255, .1);
}
.form-control-dark:focus {
border-color: transparent;
box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

6
app/static/assets/js/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,133 @@
async function fetchConfig(element) {
let xhr = new XMLHttpRequest();
xhr.open("GET", '/-/config', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
element.innerHTML = JSON.stringify(JSON.parse(xhr.response),null,2);
}
};
xhr.send();
}
async function fetchOriginsWithLeases(element) {
let xhr = new XMLHttpRequest();
xhr.open("GET", '/-/origins?leases=true', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
const x = JSON.parse(xhr.response)
console.debug(x)
element.innerHTML = ''
let table = document.createElement('table')
table.classList.add('table', 'mt-4');
let thead = document.createElement('thead');
thead.innerHTML = `
<tr>
<th scope="col">origin</th>
<th scope="col">hostname</th>
<th scope="col">OS</th>
<th scope="col">driver version</th>
<th scope="col">leases</th>
</tr>`
table.appendChild(thead)
let tbody = document.createElement('thead');
x.sort((a, b) => a.hostname.localeCompare(b.hostname)).forEach((o) => {
let row = document.createElement('tr');
row.innerHTML = `
<td><code>${o.origin_ref}</code></td>
<td>${o.hostname}</td>
<td>${o.os_platform} (${o.os_version})</td>
<td>${o.guest_driver_version}</td>
<td>${o.leases.map(x => `<code title="expires: ${x.lease_expires}">${x.lease_ref}</code>`).join(', ')}</td>`
tbody.appendChild(row);
})
table.appendChild(tbody)
element.appendChild(table)
}
};
xhr.send();
}
async function fetchLeases(element) {
// datetime config
const dtc = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
timeZoneName: "short"
}
let xhr = new XMLHttpRequest();
xhr.open("GET", '/-/leases?origin=true', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
const x = JSON.parse(xhr.response)
console.debug(x)
element.innerHTML = ''
let table = document.createElement('table')
table.classList.add('table', 'mt-4');
let thead = document.createElement('thead');
thead.innerHTML = `
<tr>
<th scope="col">lease</th>
<th scope="col">created</th>
<th scope="col">updated</th>
<th scope="col">next renew</th>
<th scope="col">expires</th>
<th scope="col">origin</th>
</tr>`
table.appendChild(thead)
let tbody = document.createElement('thead');
x.sort((a, b) => new Date(a.lease_expires) - new Date(b.lease_expires)).forEach((o) => {
let row = document.createElement('tr');
row.innerHTML = `
<td><code>${o.lease_ref}</code></td>
<td>${new Date(o.lease_created).toLocaleDateString('system', dtc)}</td>
<td>${new Date(o.lease_updated).toLocaleDateString('system', dtc)}</td>
<td>${new Date(o.lease_renewal).toLocaleDateString('system', dtc)}</td>
<td>${new Date(o.lease_expires).toLocaleDateString('system', dtc)}</td>
<td><code title="hostname: ${o.origin?.hostname}">${o.origin_ref}</code></td>`
tbody.appendChild(row);
})
table.appendChild(tbody)
element.appendChild(table)
}
};
xhr.send();
}
async function deleteOrigins() {
let xhr = new XMLHttpRequest();
xhr.open("DELETE", '/-/origins', true);
xhr.send();
}
async function deleteOrigin(origin_ref) {
if (origin_ref === undefined)
origin_ref = window.prompt("Please enter 'origin_ref' which should be deleted");
if (origin_ref === null || origin_ref === "")
return
let xhr = new XMLHttpRequest();
xhr.open("DELETE", `/-/origins/${origin_ref}`, true);
xhr.send();
}
async function deleteLease(lease_ref) {
if (lease_ref === undefined)
lease_ref = window.prompt("Please enter 'lease_ref' which should be deleted");
if (lease_ref === null || lease_ref === "")
return
let xhr = new XMLHttpRequest();
xhr.open("DELETE", `/-/lease/${lease_ref}`, true);
xhr.send();
}
async function deleteExpiredLeases() {
let xhr = new XMLHttpRequest();
xhr.open("DELETE", `/-/leases/expired`, true);
xhr.send();
}

5
app/static/assets/js/popper.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
<header class="navbar navbar-expand-md navbar-green sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="/-/">FastAPI-DLS {{ VERSION }}</a>
<button class="navbar-toggler position-absolute d-lg-none collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</header>

View File

@@ -0,0 +1,58 @@
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="position-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link {{ 'active' if request.url.path == '/-/dashboard' }}" aria-current="page" href="/-/dashboard">
<i class="bi-house-door"></i> Dashboard
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.url.path == '/-/dashboard/origins' }}" aria-current="page" href="/-/dashboard/origins">
<i class="bi-pc-display-horizontal"></i> Origins
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.url.path == '/-/dashboard/leases' }}" aria-current="page" href="/-/dashboard/leases">
<i class="bi-layers"></i> Leases
</a>
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted text-uppercase">
<span>Help</span>
</h6>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link {{ 'active' if request.url.path == '/-/readme' }}" aria-current="page" href="/-/readme">
<i class="bi-question-circle"></i> Readme
</a>
</li>
<li class="nav-item">
<a class="nav-link" aria-current="page" href="https://git.collinwebdesigns.de/oscar.krause/fastapi-dls" target="_blank">
<i class="bi-git"></i> Git Repo
</a>
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted text-uppercase">
<span>Integrations</span>
</h6>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" aria-current="page" href="/-/doc" target="_blank">
<i class="bi-file-text"></i> Swagger UI
</a>
</li>
<li class="nav-item">
<a class="nav-link" aria-current="page" href="/-/redoc" target="_blank">
<i class="bi-file-text"></i> Redoc
</a>
</li>
<li class="nav-item">
<a class="nav-link" aria-current="page" href="/-/openapi.json" target="_blank">
<i class="bi bi-filetype-json"></i> OpenAPI JSON
</a>
</li>
</ul>
</div>
</nav>

View File

@@ -0,0 +1,33 @@
<!doctype html>
<html lang="en" class="h-100">
<head>
{% block title %}
<title>FastAPI-DLS</title>
{% endblock %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
<link rel="icon" href="{{ url_for('static', path='assets/img/favicons/favicon-32x32.png') }}" sizes="32x32" type="image/png">
<link rel="icon" href="{{ url_for('static', path='assets/img/favicons/favicon-16x16.png') }}" sizes="16x16" type="image/png">
<link rel="manifest" href="{{ url_for('static', path='assets/img/favicons/manifest.json') }}">
<link rel="icon" href="{{ url_for('static', path='assets/img/favicons/favicon.ico') }}">
<link rel="apple-touch-icon" href="{{ url_for('static', path='assets/img/favicons/apple-touch-icon.png') }}" sizes="180x180">
{% block styles %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', path='assets/css/custom.css') }}">
</head>
<body class="d-flex flex-column {% block body_class %}{% endblock %}">
{% block body %}
{% endblock %}
<script src="{{ url_for('static', path='assets/js/helper.js') }}"></script>
{% block scripts %}
{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,16 @@
{% extends 'layouts/bootstrap.html' %}
{% block body %}
{% include 'components/navbar.html' %}
<div class="container-fluid">
<div class="row">
{% include 'components/sidebar.html' %}
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
{% block content %}
{% endblock %}
</main>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% extends 'layouts/base.html' %}
{% block styles %}
{{ super() }}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', path='assets/css/bootstrap.min.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', path='assets/css/bootstrap-icons.min.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', path='assets/css/dashboard.css') }}">
<script src="{{ url_for('static', path='assets/js/popper.min.js') }}"></script>
<script src="{{ url_for('static', path='assets/js/bootstrap.min.js') }}"></script>
{% endblock %}

View File

@@ -0,0 +1,50 @@
{% extends 'layouts/bootstrap-dashboard.html' %}
{% block title %}
<title>Dashboard</title>
{% endblock %}
{% block content %}
<div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Dashboard</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="downloadClientToken()">
<i class="bi bi-download"></i>
Client Token
</button>
</div>
</div>
</div>
<div class="p-5 mb-4 bg-light rounded-3">
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">FastAPI-DLS</h1>
<p class="col-md-8 fs-4">Minimal Delegated License Service (DLS).</p>
<a href="https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/releases" class="btn btn-primary btn-lg" target="_blank">
Releases &raquo;
</a>
</div>
</div>
<pre id="config"></pre>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script type="application/javascript">
function downloadClientToken() {
window.open('/-/client-token', "_blank")
}
function load() {
const config = document.getElementById('config')
fetchConfig(config)
}
load()
</script>
{% endblock %}

View File

@@ -0,0 +1,41 @@
{% extends 'layouts/bootstrap-dashboard.html' %}
{% block title %}
<title>Origins</title>
{% endblock %}
{% block content %}
<div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Leases <small>with origin</small></h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteLease().finally(() => load())">
delete lease
</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteExpiredLeases().finally(() => load())">
delete all expired leases
</button>
</div>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="load()" title="refresh">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
</div>
<div id="leases" class="mt-3"></div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script type="application/javascript">
function load() {
const leases = document.getElementById('leases')
fetchLeases(leases)
}
load()
</script>
{% endblock %}

View File

@@ -0,0 +1,48 @@
{% extends 'layouts/bootstrap-dashboard.html' %}
{% block title %}
<title>Origins</title>
{% endblock %}
{% block content %}
<div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Origins <small>with leases</small></h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteOrigin().finally(() => load())">
delete origin
</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteOriginsWrapper()">
delete all
</button>
</div>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="load()" title="refresh">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
</div>
<div id="origins" class="mt-3"></div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script type="application/javascript">
function load() {
const origins = document.getElementById('origins')
fetchOriginsWithLeases(origins)
}
load()
function deleteOriginsWrapper() {
const response = confirm('Are you sure you want to delete all origins and their leases?');
if (response)
deleteOrigins().finally(() => load())
}
</script>
{% endblock %}

View File

@@ -0,0 +1,15 @@
{% extends 'layouts/bootstrap-dashboard.html' %}
{% block title %}
<title>Origins</title>
{% endblock %}
{% block content %}
<div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<div class="overflow-hidden">
{{ markdown|safe }}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends 'layouts/bootstrap.html' %}
{% block title %}
<title>Index</title>
{% endblock %}
{% block body_class %}h-100{% endblock %}
{% block body %}
<main class="flex-shrink-0">
<div class="container">
<h1 class="mt-5 text-primary">FastAPI-DLS</h1>
<p class="lead">Minimal Delegated License Service (DLS).</p>
<p>
<a href="/-/dashboard">Dashboard</a>,
<a href="/-/readme">Readme</a>
</p>
</div>
</main>
<footer class="footer mt-auto py-3 bg-light">
<div class="container">
<span class="text-muted">FastAPI-DLS Version {{ VERSION }}</span>
</div>
</footer>
{% endblock %}

View File

@@ -1,17 +1,10 @@
import logging def load_file(filename) -> bytes:
logging.basicConfig()
def load_file(filename: str) -> bytes:
log = logging.getLogger(f'{__name__}')
log.debug(f'Loading contents of file "{filename}')
with open(filename, 'rb') as file: with open(filename, 'rb') as file:
content = file.read() content = file.read()
return content return content
def load_key(filename: str) -> "RsaKey": def load_key(filename) -> "RsaKey":
try: try:
# Crypto | Cryptodome on Debian # Crypto | Cryptodome on Debian
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
@@ -20,8 +13,6 @@ def load_key(filename: str) -> "RsaKey":
from Cryptodome.PublicKey import RSA from Cryptodome.PublicKey import RSA
from Cryptodome.PublicKey.RSA import RsaKey from Cryptodome.PublicKey.RSA import RsaKey
log = logging.getLogger(__name__)
log.debug(f'Importing RSA-Key from "{filename}"')
return RSA.import_key(extern_key=load_file(filename), passphrase=None) return RSA.import_key(extern_key=load_file(filename), passphrase=None)
@@ -33,50 +24,5 @@ def generate_key() -> "RsaKey":
except ModuleNotFoundError: except ModuleNotFoundError:
from Cryptodome.PublicKey import RSA from Cryptodome.PublicKey import RSA
from Cryptodome.PublicKey.RSA import RsaKey from Cryptodome.PublicKey.RSA import RsaKey
log = logging.getLogger(__name__)
log.debug(f'Generating RSA-Key')
return RSA.generate(bits=2048) return RSA.generate(bits=2048)
class NV:
__DRIVER_MATRIX_FILENAME = 'static/driver_matrix.json'
__DRIVER_MATRIX: None | dict = None # https://docs.nvidia.com/grid/ => "Driver Versions"
def __init__(self):
self.log = logging.getLogger(self.__class__.__name__)
if NV.__DRIVER_MATRIX is None:
from json import load as json_load
try:
file = open(NV.__DRIVER_MATRIX_FILENAME)
NV.__DRIVER_MATRIX = json_load(file)
file.close()
self.log.debug(f'Successfully loaded "{NV.__DRIVER_MATRIX_FILENAME}".')
except Exception as e:
NV.__DRIVER_MATRIX = {} # init empty dict to not try open file everytime, just when restarting app
# self.log.warning(f'Failed to load "{NV.__DRIVER_MATRIX_FILENAME}": {e}')
@staticmethod
def find(version: str) -> dict | None:
if NV.__DRIVER_MATRIX is None:
return None
for idx, (key, branch) in enumerate(NV.__DRIVER_MATRIX.items()):
for release in branch.get('$releases'):
linux_driver = release.get('Linux Driver')
windows_driver = release.get('Windows Driver')
if version == linux_driver or version == windows_driver:
tmp = branch.copy()
tmp.pop('$releases')
is_latest = release.get('vGPU Software') == branch.get('Latest Release in Branch')
return {
'software_branch': branch.get('vGPU Software Branch'),
'branch_version': release.get('vGPU Software'),
'driver_branch': branch.get('Driver Branch'),
'branch_status': branch.get('vGPU Branch Status'),
'release_date': release.get('Release Date'),
'eol': branch.get('EOL Date') if is_latest else None,
'is_latest': is_latest,
}
return None

View File

@@ -1,8 +1,9 @@
fastapi==0.115.6 fastapi==0.99.1
uvicorn[standard]==0.34.0 uvicorn[standard]==0.22.0
python-jose==3.3.0 python-jose==3.3.0
pycryptodome==3.21.0 pycryptodome==3.18.0
python-dateutil==2.8.2 python-dateutil==2.8.2
sqlalchemy==2.0.37 sqlalchemy==2.0.17
markdown==3.7 markdown==3.4.3
python-dotenv==1.0.1 python-dotenv==1.0.0
jinja2==3.1.2

View File

@@ -1,137 +0,0 @@
import logging
logging.basicConfig()
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
URL = 'https://docs.nvidia.com/vgpu/index.html'
BRANCH_STATUS_KEY, SOFTWARE_BRANCH_KEY, = 'vGPU Branch Status', 'vGPU Software Branch'
VGPU_KEY, GRID_KEY, DRIVER_BRANCH_KEY = 'vGPU Software', 'vGPU Software', 'Driver Branch'
LINUX_VGPU_MANAGER_KEY, LINUX_DRIVER_KEY = 'Linux vGPU Manager', 'Linux Driver'
WINDOWS_VGPU_MANAGER_KEY, WINDOWS_DRIVER_KEY = 'Windows vGPU Manager', 'Windows Driver'
ALT_VGPU_MANAGER_KEY = 'vGPU Manager'
RELEASE_DATE_KEY, LATEST_KEY, EOL_KEY = 'Release Date', 'Latest Release in Branch', 'EOL Date'
JSON_RELEASES_KEY = '$releases'
def __driver_versions(html: 'BeautifulSoup'):
def __strip(_: str) -> str:
# removes content after linebreak (e.g. "Hello\n World" to "Hello")
_ = _.strip()
tmp = _.split('\n')
if len(tmp) > 0:
return tmp[0]
return _
# find wrapper for "DriverVersions" and find tables
data = html.find('div', {'id': 'driver-versions'})
items = data.findAll('bsp-accordion', {'class': 'Accordion-items-item'})
for item in items:
software_branch = item.find('div', {'class': 'Accordion-items-item-title'}).text.strip()
software_branch = software_branch.replace(' Releases', '')
matrix_key = software_branch.lower()
# driver version info from table-heads (ths) and table-rows (trs)
table = item.find('table')
ths, trs = table.find_all('th'), table.find_all('tr')
headers, releases = [header.text.strip() for header in ths], []
for trs in trs:
tds = trs.find_all('td')
if len(tds) == 0: # skip empty
continue
# create dict with table-heads as key and cell content as value
x = {headers[i]: __strip(cell.text) for i, cell in enumerate(tds)}
releases.append(x)
# add to matrix
MATRIX.update({matrix_key: {JSON_RELEASES_KEY: releases}})
def __release_branches(html: 'BeautifulSoup'):
# find wrapper for "AllReleaseBranches" and find table
data = html.find('div', {'id': 'all-release-branches'})
table = data.find('table')
# branch releases info from table-heads (ths) and table-rows (trs)
ths, trs = table.find_all('th'), table.find_all('tr')
headers = [header.text.strip() for header in ths]
for trs in trs:
tds = trs.find_all('td')
if len(tds) == 0: # skip empty
continue
# create dict with table-heads as key and cell content as value
x = {headers[i]: cell.text.strip() for i, cell in enumerate(tds)}
# get matrix_key
software_branch = x.get(SOFTWARE_BRANCH_KEY)
matrix_key = software_branch.lower()
# add to matrix
MATRIX.update({matrix_key: MATRIX.get(matrix_key) | x})
def __debug():
# print table head
s = f'{SOFTWARE_BRANCH_KEY:^21} | {BRANCH_STATUS_KEY:^21} | {VGPU_KEY:^13} | {LINUX_VGPU_MANAGER_KEY:^21} | {LINUX_DRIVER_KEY:^21} | {WINDOWS_VGPU_MANAGER_KEY:^21} | {WINDOWS_DRIVER_KEY:^21} | {RELEASE_DATE_KEY:>21} | {EOL_KEY:>21}'
print(s)
# iterate over dict & format some variables to not overload table
for idx, (key, branch) in enumerate(MATRIX.items()):
branch_status = branch.get(BRANCH_STATUS_KEY)
branch_status = branch_status.replace('Branch ', '')
branch_status = branch_status.replace('Long-Term Support', 'LTS')
branch_status = branch_status.replace('Production', 'Prod.')
software_branch = branch.get(SOFTWARE_BRANCH_KEY).replace('NVIDIA ', '')
for release in branch.get(JSON_RELEASES_KEY):
version = release.get(VGPU_KEY, release.get(GRID_KEY, ''))
linux_manager = release.get(LINUX_VGPU_MANAGER_KEY, release.get(ALT_VGPU_MANAGER_KEY, ''))
linux_driver = release.get(LINUX_DRIVER_KEY)
windows_manager = release.get(WINDOWS_VGPU_MANAGER_KEY, release.get(ALT_VGPU_MANAGER_KEY, ''))
windows_driver = release.get(WINDOWS_DRIVER_KEY)
release_date = release.get(RELEASE_DATE_KEY)
is_latest = release.get(VGPU_KEY) == branch.get(LATEST_KEY)
version = f'{version} *' if is_latest else version
eol = branch.get(EOL_KEY) if is_latest else ''
s = f'{software_branch:^21} | {branch_status:^21} | {version:<13} | {linux_manager:<21} | {linux_driver:<21} | {windows_manager:<21} | {windows_driver:<21} | {release_date:>21} | {eol:>21}'
print(s)
def __dump(filename: str):
import json
file = open(filename, 'w')
json.dump(MATRIX, file)
file.close()
if __name__ == '__main__':
MATRIX = {}
try:
import httpx
from bs4 import BeautifulSoup
except Exception as e:
logger.error(f'Failed to import module: {e}')
logger.info('Run "pip install beautifulsoup4 httpx"')
exit(1)
r = httpx.get(URL)
if r.status_code != 200:
logger.error(f'Error loading "{URL}" with status code {r.status_code}.')
exit(2)
# parse html
soup = BeautifulSoup(r.text, features='html.parser')
# build matrix
__driver_versions(soup)
__release_branches(soup)
# debug output
__debug()
# dump data to file
__dump('../app/static/driver_matrix.json')

View File

@@ -1,8 +1,7 @@
import sys
from base64 import b64encode as b64enc from base64 import b64encode as b64enc
from calendar import timegm
from datetime import datetime, UTC
from hashlib import sha256 from hashlib import sha256
from calendar import timegm
from datetime import datetime
from os.path import dirname, join from os.path import dirname, join
from uuid import uuid4, UUID from uuid import uuid4, UUID
@@ -10,6 +9,7 @@ from dateutil.relativedelta import relativedelta
from jose import jwt, jwk from jose import jwt, jwk
from jose.constants import ALGORITHMS from jose.constants import ALGORITHMS
from starlette.testclient import TestClient from starlette.testclient import TestClient
import sys
# add relative path to use packages as they were in the app/ dir # add relative path to use packages as they were in the app/ dir
sys.path.append('../') sys.path.append('../')
@@ -59,8 +59,8 @@ def test_readme():
assert response.status_code == 200 assert response.status_code == 200
def test_manage(): def test_dashboard():
response = client.get('/-/manage') response = client.get('/-/dashboard')
assert response.status_code == 200 assert response.status_code == 200
@@ -106,7 +106,6 @@ def test_auth_v1_origin():
assert response.json().get('origin_ref') == ORIGIN_REF assert response.json().get('origin_ref') == ORIGIN_REF
def auth_v1_origin_update(): def auth_v1_origin_update():
payload = { payload = {
"registration_pending": False, "registration_pending": False,
@@ -142,7 +141,7 @@ def test_auth_v1_code():
def test_auth_v1_token(): def test_auth_v1_token():
cur_time = datetime.now(UTC) cur_time = datetime.utcnow()
access_expires_on = cur_time + relativedelta(hours=1) access_expires_on = cur_time + relativedelta(hours=1)
payload = { payload = {
@@ -154,7 +153,8 @@ def test_auth_v1_token():
"kid": "00000000-0000-0000-0000-000000000000" "kid": "00000000-0000-0000-0000-000000000000"
} }
payload = { payload = {
"auth_code": jwt.encode(payload, key=jwt_encode_key, headers={'kid': payload.get('kid')}, algorithm=ALGORITHMS.RS256), "auth_code": jwt.encode(payload, key=jwt_encode_key, headers={'kid': payload.get('kid')},
algorithm=ALGORITHMS.RS256),
"code_verifier": SECRET, "code_verifier": SECRET,
} }