forked from oscar.krause/fastapi-dls
Merge branch 'dev' into 'main'
1.1 See merge request oscar.krause/fastapi-dls!14
This commit is contained in:
commit
0b7bedde66
@ -1,5 +1,5 @@
|
|||||||
Package: fastapi-dls
|
Package: fastapi-dls
|
||||||
Version: 1.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-jose, 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, uvicorn, openssl
|
@ -41,10 +41,29 @@ if [[ ! -f $CONFIG_DIR/env ]]; then
|
|||||||
echo "> Writing initial config ..."
|
echo "> Writing initial config ..."
|
||||||
touch $CONFIG_DIR/env
|
touch $CONFIG_DIR/env
|
||||||
cat <<EOF >$CONFIG_DIR/env
|
cat <<EOF >$CONFIG_DIR/env
|
||||||
|
# Toggle debug mode
|
||||||
|
#DEBUG=false
|
||||||
|
|
||||||
|
# Where the client can find the DLS server
|
||||||
DLS_URL=127.0.0.1
|
DLS_URL=127.0.0.1
|
||||||
DLS_PORT=443
|
DLS_PORT=443
|
||||||
|
|
||||||
|
# CORS configuration
|
||||||
|
## comma separated list without spaces
|
||||||
|
#CORS_ORIGINS="https://$DLS_URL:$DLS_PORT"
|
||||||
|
|
||||||
|
# Lease expiration in days
|
||||||
LEASE_EXPIRE_DAYS=90
|
LEASE_EXPIRE_DAYS=90
|
||||||
|
|
||||||
|
# Database location
|
||||||
|
## https://docs.sqlalchemy.org/en/14/core/engines.html
|
||||||
DATABASE=sqlite:///$CONFIG_DIR/db.sqlite
|
DATABASE=sqlite:///$CONFIG_DIR/db.sqlite
|
||||||
|
|
||||||
|
# UUIDs for identifying the instance
|
||||||
|
#SITE_KEY_XID="00000000-0000-0000-0000-000000000000"
|
||||||
|
#INSTANCE_REF="00000000-0000-0000-0000-000000000000"
|
||||||
|
|
||||||
|
# Site-wide signing keys
|
||||||
INSTANCE_KEY_RSA=$CONFIG_DIR/instance.private.pem
|
INSTANCE_KEY_RSA=$CONFIG_DIR/instance.private.pem
|
||||||
INSTANCE_KEY_PUB=$CONFIG_DIR/instance.public.pem
|
INSTANCE_KEY_PUB=$CONFIG_DIR/instance.public.pem
|
||||||
|
|
||||||
@ -75,7 +94,7 @@ if [[ -f $CONFIG_DIR/webserver.key ]]; then
|
|||||||
if [ -x "$(command -v curl)" ]; then
|
if [ -x "$(command -v curl)" ]; then
|
||||||
echo "> Testing API ..."
|
echo "> Testing API ..."
|
||||||
source $CONFIG_DIR/env
|
source $CONFIG_DIR/env
|
||||||
curl --insecure -X GET https://$DLS_URL:$DLS_PORT/status
|
curl --insecure -X GET https://$DLS_URL:$DLS_PORT/-/health
|
||||||
else
|
else
|
||||||
echo "> Testing API failed, curl not available. Please test manually!"
|
echo "> Testing API failed, curl not available. Please test manually!"
|
||||||
fi
|
fi
|
49
.PKGBUILD/PKGBUILD
Normal file
49
.PKGBUILD/PKGBUILD
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# Maintainer: samicrusader <hi@samicrusader.me>
|
||||||
|
# Maintainer: Oscar Krause <oscar.krause@collinwebdesigns.de>
|
||||||
|
|
||||||
|
pkgname=fastapi-dls
|
||||||
|
pkgver=0.0
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc='NVIDIA DLS server implementation with FastAPI'
|
||||||
|
arch=('any')
|
||||||
|
url='https://git.collinwebdesigns.de/oscar.krause/fastapi-dls'
|
||||||
|
license=('MIT')
|
||||||
|
depends=('python' 'python-jose' 'python-starlette' 'python-httpx' 'python-fastapi' 'python-dotenv' 'python-dateutil' 'python-sqlalchemy' 'python-pycryptodome' 'uvicorn' 'python-markdown' 'openssl')
|
||||||
|
provider=("$pkgname")
|
||||||
|
install="$pkgname.install"
|
||||||
|
source=('git+file:///builds/oscar.krause/fastapi-dls' # https://gitea.publichub.eu/oscar.krause/fastapi-dls.git
|
||||||
|
"$pkgname.default"
|
||||||
|
"$pkgname.service")
|
||||||
|
sha256sums=('SKIP'
|
||||||
|
'4c07e9b627853bd4f3a398371912fc72302dac33f43e4cb7e9b79746cc9c9136'
|
||||||
|
'10cb98d64f8bf37b11a60510793c187cc664e63c895d1205781c21fa2e703f32')
|
||||||
|
|
||||||
|
pkgver() {
|
||||||
|
source $srcdir/$pkgname/version.env
|
||||||
|
echo ${VERSION}
|
||||||
|
}
|
||||||
|
|
||||||
|
check() {
|
||||||
|
cd "$srcdir/$pkgname/test"
|
||||||
|
mkdir "$srcdir/$pkgname/app/cert"
|
||||||
|
openssl genrsa -out "$srcdir/$pkgname/app/cert/instance.private.pem" 2048
|
||||||
|
openssl rsa -in "$srcdir/$pkgname/app/cert/instance.private.pem" -outform PEM -pubout -out "$srcdir/$pkgname/app/cert/instance.public.pem"
|
||||||
|
python "$srcdir/$pkgname/test/main.py"
|
||||||
|
rm -rf "$srcdir/$pkgname/app/cert"
|
||||||
|
}
|
||||||
|
|
||||||
|
package() {
|
||||||
|
install -d "$pkgdir/usr/share/doc/$pkgname"
|
||||||
|
install -d "$pkgdir/var/lib/$pkgname/cert"
|
||||||
|
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/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/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/orm.py" "$pkgdir/opt/$pkgname/orm.py"
|
||||||
|
install -Dm755 "$srcdir/$pkgname/app/util.py" "$pkgdir/opt/$pkgname/util.py"
|
||||||
|
install -Dm644 "$srcdir/$pkgname.default" "$pkgdir/etc/default/$pkgname"
|
||||||
|
install -Dm644 "$srcdir/$pkgname.service" "$pkgdir/usr/lib/systemd/system/$pkgname.service"
|
||||||
|
}
|
23
.PKGBUILD/fastapi-dls.default
Normal file
23
.PKGBUILD/fastapi-dls.default
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Toggle FastAPI debug mode
|
||||||
|
DEBUG=false
|
||||||
|
|
||||||
|
# Where the client can find the DLS server
|
||||||
|
## DLS_URL should be a hostname
|
||||||
|
DLS_URL="localhost.localdomain"
|
||||||
|
DLS_PORT=8443
|
||||||
|
CORS_ORIGINS="https://$DLS_URL:$DLS_PORT"
|
||||||
|
|
||||||
|
# Lease expiration in days
|
||||||
|
LEASE_EXPIRE_DAYS=90
|
||||||
|
|
||||||
|
# Database location
|
||||||
|
## https://docs.sqlalchemy.org/en/14/core/engines.html
|
||||||
|
DATABASE="sqlite:////var/lib/fastapi-dls/db.sqlite"
|
||||||
|
|
||||||
|
# UUIDs for identifying the instance
|
||||||
|
SITE_KEY_XID="<<sitekey>>"
|
||||||
|
INSTANCE_REF="<<instanceref>>"
|
||||||
|
|
||||||
|
# Site-wide signing keys
|
||||||
|
INSTANCE_KEY_RSA="/var/lib/fastapi-dls/instance.private.pem"
|
||||||
|
INSTANCE_KEY_PUB="/var/lib/fastapi-dls/instance.public.pem"
|
14
.PKGBUILD/fastapi-dls.install
Normal file
14
.PKGBUILD/fastapi-dls.install
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
post_install() {
|
||||||
|
sed -i "s/<<sitekey>>/$(uuidgen)/" /etc/default/fastapi-dls
|
||||||
|
sed -i "s/<<instanceref>>/$(uuidgen)/" /etc/default/fastapi-dls
|
||||||
|
|
||||||
|
echo 'The environment variables for this server can be edited at: /etc/default/fastapi-dls'
|
||||||
|
echo 'The server can be started with: systemctl start fastapi-dls.service'
|
||||||
|
echo
|
||||||
|
echo 'A valid HTTPS certificate needs to be installed to /var/lib/fastapi-dls/cert/webserver.{crt,key}'
|
||||||
|
echo 'A self-signed certificate can be generated with: openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /var/lib/fastapi-dls/cert/webserver.key -out /var/lib/fastapi-dls/cert/webserver.crt'
|
||||||
|
echo
|
||||||
|
echo 'The signing keys for your instance need to be generated as well. Generate them with these commands:'
|
||||||
|
echo 'openssl genrsa -out /var/lib/fastapi-dls/instance.private.pem 2048'
|
||||||
|
echo 'openssl rsa -in /var/lib/fastapi-dls/instance.private.pem -outform PEM -pubout -out /var/lib/fastapi-dls/instance.public.pem'
|
||||||
|
}
|
15
.PKGBUILD/fastapi-dls.service
Normal file
15
.PKGBUILD/fastapi-dls.service
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=FastAPI-DLS
|
||||||
|
Documentation=https://git.collinwebdesigns.de/oscar.krause/fastapi-dls
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=forking
|
||||||
|
EnvironmentFile=/etc/default/fastapi-dls
|
||||||
|
ExecStart=/usr/bin/python /opt/fastapi-dls/main.py
|
||||||
|
WorkingDir=/opt/fastapi-dls
|
||||||
|
Restart=on-abort
|
||||||
|
User=root
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
191
.gitlab-ci.yml
191
.gitlab-ci.yml
@ -1,17 +1,45 @@
|
|||||||
cache:
|
cache:
|
||||||
key: one-key-to-rule-them-all
|
key: one-key-to-rule-them-all
|
||||||
|
|
||||||
build:debian:
|
build:docker:
|
||||||
# debian:bullseye-slim
|
image: docker:dind
|
||||||
image: debian:bookworm-slim # just to get "python3-jose" working
|
interruptible: true
|
||||||
stage: build
|
stage: build
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
|
||||||
|
changes:
|
||||||
|
- app/**/*
|
||||||
|
- Dockerfile
|
||||||
|
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
|
||||||
|
tags: [ docker ]
|
||||||
before_script:
|
before_script:
|
||||||
|
- echo "COMMIT=${CI_COMMIT_SHA}" >> version.env # COMMIT=`git rev-parse HEAD`
|
||||||
|
script:
|
||||||
|
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||||
|
- docker build . --tag ${CI_REGISTRY}/${CI_PROJECT_PATH}/${CI_BUILD_REF_NAME}:${CI_BUILD_REF}
|
||||||
|
- docker push ${CI_REGISTRY}/${CI_PROJECT_PATH}/${CI_BUILD_REF_NAME}:${CI_BUILD_REF}
|
||||||
|
|
||||||
|
build:apt:
|
||||||
|
image: debian:bookworm-slim
|
||||||
|
interruptible: true
|
||||||
|
stage: build
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
|
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
|
||||||
|
changes:
|
||||||
|
- app/**/*
|
||||||
|
- .DEBIAN/**/*
|
||||||
|
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
|
||||||
|
before_script:
|
||||||
|
- echo "COMMIT=${CI_COMMIT_SHA}" >> version.env
|
||||||
|
- source version.env
|
||||||
|
# install build dependencies
|
||||||
- apt-get update -qq && apt-get install -qq -y build-essential
|
- apt-get update -qq && apt-get install -qq -y build-essential
|
||||||
- chmod 0755 -R .
|
|
||||||
# create build directory for .deb sources
|
# create build directory for .deb sources
|
||||||
- mkdir build
|
- mkdir build
|
||||||
# copy install instructions
|
# copy install instructions
|
||||||
- cp -r DEBIAN build/
|
- cp -r .DEBIAN build/DEBIAN
|
||||||
|
- chmod -R 0775 build/DEBIAN
|
||||||
# copy app into "/usr/share/fastapi-dls" as "/usr/share/fastapi-dls/app" & copy README.md and version.env
|
# copy app into "/usr/share/fastapi-dls" as "/usr/share/fastapi-dls/app" & copy README.md and version.env
|
||||||
- mkdir -p build/usr/share/fastapi-dls
|
- mkdir -p build/usr/share/fastapi-dls
|
||||||
- cp -r app build/usr/share/fastapi-dls
|
- cp -r app build/usr/share/fastapi-dls
|
||||||
@ -22,29 +50,55 @@ build:debian:
|
|||||||
# cd into "build/"
|
# cd into "build/"
|
||||||
- cd build/
|
- cd build/
|
||||||
script:
|
script:
|
||||||
|
# set version based on value in "$VERSION" (which is set above from version.env)
|
||||||
|
- sed -i -E 's/(Version\:\s)0.0/\1'"$VERSION"'/g' DEBIAN/control
|
||||||
|
# build
|
||||||
- dpkg -b . build.deb
|
- dpkg -b . build.deb
|
||||||
|
- dpkg -I build.deb
|
||||||
artifacts:
|
artifacts:
|
||||||
expire_in: 1 week
|
expire_in: 1 week
|
||||||
paths:
|
paths:
|
||||||
- build/build.deb
|
- build/build.deb
|
||||||
|
|
||||||
build:docker:
|
build:pacman:
|
||||||
image: docker:dind
|
image: archlinux:base-devel
|
||||||
interruptible: true
|
interruptible: true
|
||||||
stage: build
|
stage: build
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
tags: [ docker ]
|
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
|
||||||
|
changes:
|
||||||
|
- app/**/*
|
||||||
|
- .PKGBUILD/**/*
|
||||||
|
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
|
||||||
before_script:
|
before_script:
|
||||||
- echo "COMMIT=${CI_COMMIT_SHA}" >> version.env # COMMIT=`git rev-parse HEAD`
|
- echo "COMMIT=${CI_COMMIT_SHA}" >> version.env
|
||||||
|
# install build dependencies
|
||||||
|
- pacman -Syu --noconfirm git
|
||||||
|
# create a build-user because "makepkg" don't like root user
|
||||||
|
- useradd --no-create-home --shell=/bin/false build && usermod -L build
|
||||||
|
- 'echo "build ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers'
|
||||||
|
- 'echo "root ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers'
|
||||||
|
- chown -R build:build .
|
||||||
|
# move .PKGBUILD contents to root directory
|
||||||
|
- mv .PKGBUILD/* .
|
||||||
script:
|
script:
|
||||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
- pwd
|
||||||
- docker build . --tag ${CI_REGISTRY}/${CI_PROJECT_PATH}/${CI_BUILD_REF_NAME}:${CI_BUILD_REF}
|
# download dependencies
|
||||||
- docker push ${CI_REGISTRY}/${CI_PROJECT_PATH}/${CI_BUILD_REF_NAME}:${CI_BUILD_REF}
|
- source PKGBUILD && pacman -Syu --noconfirm --needed --asdeps "${makedepends[@]}" "${depends[@]}"
|
||||||
|
# build
|
||||||
|
- sudo -u build makepkg -s
|
||||||
|
artifacts:
|
||||||
|
expire_in: 1 week
|
||||||
|
paths:
|
||||||
|
- "*.pkg.tar.zst"
|
||||||
|
|
||||||
test:
|
test:
|
||||||
image: python:3.10-slim-bullseye
|
image: python:3.10-slim-bullseye
|
||||||
stage: test
|
stage: test
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH
|
||||||
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
variables:
|
variables:
|
||||||
DATABASE: sqlite:///../app/db.sqlite
|
DATABASE: sqlite:///../app/db.sqlite
|
||||||
before_script:
|
before_script:
|
||||||
@ -57,37 +111,75 @@ test:
|
|||||||
script:
|
script:
|
||||||
- pytest main.py
|
- pytest main.py
|
||||||
|
|
||||||
test:debian:
|
.test:linux:
|
||||||
image: debian:bookworm-slim
|
|
||||||
stage: test
|
stage: test
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
|
||||||
|
changes:
|
||||||
|
- app/**/*
|
||||||
|
- .DEBIAN/**/*
|
||||||
|
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
|
||||||
|
needs:
|
||||||
|
- job: build:apt
|
||||||
|
artifacts: true
|
||||||
variables:
|
variables:
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
needs:
|
|
||||||
- job: build:debian
|
|
||||||
artifacts: true
|
|
||||||
before_script:
|
before_script:
|
||||||
- apt-get update -qq && apt-get install -qq -y jq
|
- apt-get update -qq && apt-get install -qq -y jq curl
|
||||||
script:
|
script:
|
||||||
# test installation
|
# test installation
|
||||||
- apt-get install -q -y ./build/build.deb --fix-missing
|
- apt-get install -q -y ./build/build.deb --fix-missing
|
||||||
|
- openssl req -x509 -newkey rsa:2048 -nodes -out /etc/fastapi-dls/webserver.crt -keyout /etc/fastapi-dls/webserver.key -days 7 -subj "/C=DE/O=GitLab-CI/OU=Test/CN=localhost"
|
||||||
# copy example config from GitLab-CI-Variables
|
# copy example config from GitLab-CI-Variables
|
||||||
#- cat ${EXAMPLE_CONFIG} > /etc/fastapi-dls/env
|
#- cat ${EXAMPLE_CONFIG} > /etc/fastapi-dls/env
|
||||||
# start service in background
|
# start service in background
|
||||||
- uvicorn --host 127.0.0.1 --port 443
|
- cd /usr/share/fastapi-dls/app
|
||||||
|
- uvicorn main:app
|
||||||
|
--host 127.0.0.1 --port 443
|
||||||
--app-dir /usr/share/fastapi-dls/app
|
--app-dir /usr/share/fastapi-dls/app
|
||||||
--ssl-keyfile /etc/fastapi-dls/webserver.key
|
--ssl-keyfile /etc/fastapi-dls/webserver.key
|
||||||
--ssl-certfile /opt/fastapi-dls/webserver.crt
|
--ssl-certfile /etc/fastapi-dls/webserver.crt
|
||||||
--proxy-headers &
|
--proxy-headers &
|
||||||
- FASTAPI_DLS_PID=$!
|
- FASTAPI_DLS_PID=$!
|
||||||
- echo "Started service with pid $FASTAPI_DLS_PID"
|
- echo "Started service with pid $FASTAPI_DLS_PID"
|
||||||
# testing service
|
# testing service
|
||||||
- if [ "`curl --insecure -s https://127.0.0.1/status | jq .status`" != "up" ]; then echo "Success"; else "Error"; fi
|
- if [ "`curl --insecure -s https://127.0.0.1/-/health | jq .status`" != "up" ]; then echo "Success"; else "Error"; fi
|
||||||
# cleanup
|
# cleanup
|
||||||
- kill $FASTAPI_DLS_PID
|
- kill $FASTAPI_DLS_PID
|
||||||
- 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:debian:
|
||||||
|
extends: .test:linux
|
||||||
|
image: debian:bookworm-slim
|
||||||
|
|
||||||
|
test:ubuntu:
|
||||||
|
extends: .test:linux
|
||||||
|
image: ubuntu:22.10
|
||||||
|
|
||||||
|
test:archlinux:
|
||||||
|
image: archlinux:base
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
|
||||||
|
changes:
|
||||||
|
- app/**/*
|
||||||
|
- .PKGBUILD/**/*
|
||||||
|
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
|
||||||
|
needs:
|
||||||
|
- job: build:pacman
|
||||||
|
artifacts: true
|
||||||
|
script:
|
||||||
|
- pacman -Sy
|
||||||
|
- pacman -U --noconfirm *.pkg.tar.zst
|
||||||
|
|
||||||
|
.deploy:
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
|
- if: $CI_COMMIT_TAG
|
||||||
|
when: never
|
||||||
|
|
||||||
deploy:docker:
|
deploy:docker:
|
||||||
|
extends: .deploy
|
||||||
stage: deploy
|
stage: deploy
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
@ -109,14 +201,15 @@ deploy:docker:
|
|||||||
- docker push $PUBLIC_REGISTRY_USER/${CI_PROJECT_NAME}:${VERSION}
|
- docker push $PUBLIC_REGISTRY_USER/${CI_PROJECT_NAME}:${VERSION}
|
||||||
- docker push $PUBLIC_REGISTRY_USER/${CI_PROJECT_NAME}:latest
|
- docker push $PUBLIC_REGISTRY_USER/${CI_PROJECT_NAME}:latest
|
||||||
|
|
||||||
deploy:debian:
|
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
|
||||||
|
extends: .deploy
|
||||||
image: debian:bookworm-slim
|
image: debian:bookworm-slim
|
||||||
stage: deploy
|
stage: deploy
|
||||||
# rules:
|
rules:
|
||||||
# - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
needs:
|
needs:
|
||||||
- job: build:debian
|
- job: build:apt
|
||||||
artifacts: true
|
artifacts: true
|
||||||
before_script:
|
before_script:
|
||||||
- apt-get update -qq && apt-get install -qq -y curl lsb-release
|
- apt-get update -qq && apt-get install -qq -y curl lsb-release
|
||||||
@ -149,3 +242,49 @@ deploy:debian:
|
|||||||
# using generic-package-registry until debian-registry is GA
|
# using generic-package-registry until debian-registry is GA
|
||||||
# https://docs.gitlab.com/ee/user/packages/generic_packages/index.html#publish-a-generic-package-by-using-cicd
|
# https://docs.gitlab.com/ee/user/packages/generic_packages/index.html#publish-a-generic-package-by-using-cicd
|
||||||
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ${EXPORT_NAME} "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGE_NAME}/${PACKAGE_VERSION}/${EXPORT_NAME}"'
|
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ${EXPORT_NAME} "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGE_NAME}/${PACKAGE_VERSION}/${EXPORT_NAME}"'
|
||||||
|
|
||||||
|
deploy:pacman:
|
||||||
|
extends: .deploy
|
||||||
|
image: archlinux:base-devel
|
||||||
|
stage: deploy
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
|
needs:
|
||||||
|
- job: build:pacman
|
||||||
|
artifacts: true
|
||||||
|
script:
|
||||||
|
- source .PKGBUILD/PKGBUILD
|
||||||
|
# fastapi-dls-1.0-1-any.pkg.tar.zst
|
||||||
|
- BUILD_NAME=${pkgname}-${pkgver}-${pkgrel}-any.pkg.tar.zst
|
||||||
|
- PACKAGE_NAME=${pkgname}
|
||||||
|
- PACKAGE_VERSION=${pkgver}
|
||||||
|
- PACKAGE_ARCH=any
|
||||||
|
- EXPORT_NAME=${BUILD_NAME}
|
||||||
|
- 'echo "PACKAGE_NAME: ${PACKAGE_NAME}"'
|
||||||
|
- 'echo "PACKAGE_VERSION: ${PACKAGE_VERSION}"'
|
||||||
|
- 'echo "PACKAGE_ARCH: ${PACKAGE_ARCH}"'
|
||||||
|
- 'echo "EXPORT_NAME: ${EXPORT_NAME}"'
|
||||||
|
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ${EXPORT_NAME} "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGE_NAME}/${PACKAGE_VERSION}/${EXPORT_NAME}"'
|
||||||
|
|
||||||
|
release:
|
||||||
|
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||||
|
stage: .post
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_TAG
|
||||||
|
when: never
|
||||||
|
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||||
|
before_script:
|
||||||
|
- source version.env
|
||||||
|
script:
|
||||||
|
- echo "Running release-job for $VERSION"
|
||||||
|
release:
|
||||||
|
name: $CI_PROJECT_TITLE $version
|
||||||
|
description: Release of $CI_PROJECT_TITLE version $VERSION
|
||||||
|
tag_name: $VERSION
|
||||||
|
ref: $CI_COMMIT_SHA
|
||||||
|
assets:
|
||||||
|
links:
|
||||||
|
- name: 'Package Registry'
|
||||||
|
url: 'https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/packages'
|
||||||
|
- name: 'Container Registry'
|
||||||
|
url: 'https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/container_registry/40'
|
||||||
|
2
CODEOWNERS
Executable file
2
CODEOWNERS
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
* @oscar.krause
|
||||||
|
.PKGBUILD/ @samicrusader
|
@ -14,5 +14,5 @@ COPY app /app
|
|||||||
COPY version.env /version.env
|
COPY version.env /version.env
|
||||||
COPY README.md /README.md
|
COPY README.md /README.md
|
||||||
|
|
||||||
HEALTHCHECK --start-period=30s --interval=10s --timeout=5s --retries=3 CMD curl --insecure --fail https://localhost/status || exit 1
|
HEALTHCHECK --start-period=30s --interval=10s --timeout=5s --retries=3 CMD curl --insecure --fail https://localhost/-/health || exit 1
|
||||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "443", "--app-dir", "/app", "--proxy-headers", "--ssl-keyfile", "/app/cert/webserver.key", "--ssl-certfile", "/app/cert/webserver.crt"]
|
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "443", "--app-dir", "/app", "--proxy-headers", "--ssl-keyfile", "/app/cert/webserver.key", "--ssl-certfile", "/app/cert/webserver.crt"]
|
||||||
|
73
README.md
73
README.md
@ -9,31 +9,58 @@ Only the clients need a connection to this service on configured port.
|
|||||||
|
|
||||||
## ToDo's
|
## ToDo's
|
||||||
|
|
||||||
- migrate from `fastapi` to `flask`
|
|
||||||
- Support http mode for using external https proxy (disable uvicorn ssl for using behind proxy)
|
- Support http mode for using external https proxy (disable uvicorn ssl for using behind proxy)
|
||||||
|
|
||||||
## Endpoints
|
## Endpoints
|
||||||
|
|
||||||
### `GET /`
|
### [`GET /`](/)
|
||||||
|
|
||||||
HTML rendered README.md.
|
Redirect to `/-/readme`.
|
||||||
|
|
||||||
### `GET /status`
|
### [`GET /status`](/status) (deprecated: use `/-/health`)
|
||||||
|
|
||||||
Status endpoint, used for *healthcheck*. Shows also current version and commit hash.
|
Status endpoint, used for *healthcheck*. Shows also current version and commit hash.
|
||||||
|
|
||||||
### `GET /docs`
|
### [`GET /-/health`](/-/health)
|
||||||
|
|
||||||
OpenAPI specifications rendered from `GET /openapi.json`.
|
Status endpoint, used for *healthcheck*. Shows also current version and commit hash.
|
||||||
|
|
||||||
### `GET /-/origins`
|
### [`GET /-/readme`](/-/readme)
|
||||||
|
|
||||||
|
HTML rendered README.md.
|
||||||
|
|
||||||
|
### [`GET /-/docs`](/-/docs), [`GET /-/redocs`](/-/redocs)
|
||||||
|
|
||||||
|
OpenAPI specifications rendered from `GET /-/openapi.json`.
|
||||||
|
|
||||||
|
### [`GET /-/manage`](/-/manage)
|
||||||
|
|
||||||
|
Shows a very basic UI to delete origins or leases.
|
||||||
|
|
||||||
|
### `GET /-/origins?leases=false`
|
||||||
|
|
||||||
List registered origins.
|
List registered origins.
|
||||||
|
|
||||||
### `GET /-/leases`
|
| Query Parameter | Default | Usage |
|
||||||
|
|-----------------|---------|--------------------------------------|
|
||||||
|
| `leases` | `false` | Include referenced leases per origin |
|
||||||
|
|
||||||
|
### `DELETE /-/origins`
|
||||||
|
|
||||||
|
Deletes all origins and their leases.
|
||||||
|
|
||||||
|
### `GET /-/leases?origin=false`
|
||||||
|
|
||||||
List current leases.
|
List current leases.
|
||||||
|
|
||||||
|
| Query Parameter | Default | Usage |
|
||||||
|
|-----------------|---------|-------------------------------------|
|
||||||
|
| `origin` | `false` | Include referenced origin per lease |
|
||||||
|
|
||||||
|
### `DELETE /-/lease/{lease_ref}`
|
||||||
|
|
||||||
|
Deletes an lease.
|
||||||
|
|
||||||
### `GET /client-token`
|
### `GET /client-token`
|
||||||
|
|
||||||
Generate client token, (see [installation](#installation)).
|
Generate client token, (see [installation](#installation)).
|
||||||
@ -200,7 +227,7 @@ Packages are available here:
|
|||||||
|
|
||||||
Successful tested with:
|
Successful tested with:
|
||||||
|
|
||||||
- Debian 12 (Bookworm)
|
- Debian 12 (Bookworm) (works but not recommended because it is currently in *testing* state)
|
||||||
- Ubuntu 22.10 (Kinetic Kudu)
|
- Ubuntu 22.10 (Kinetic Kudu)
|
||||||
|
|
||||||
**Run this on your server instance**
|
**Run this on your server instance**
|
||||||
@ -218,6 +245,23 @@ apt-get install -f --fix-missing
|
|||||||
|
|
||||||
Start with `systemctl start fastapi-dls.service` and enable autostart with `systemctl enable fastapi-dls.service`.
|
Start with `systemctl start fastapi-dls.service` and enable autostart with `systemctl enable fastapi-dls.service`.
|
||||||
|
|
||||||
|
## ArchLinux (using `pacman`)
|
||||||
|
|
||||||
|
**Shout out to `samicrusader` who created build file for ArchLinux!**
|
||||||
|
|
||||||
|
Packages are available here:
|
||||||
|
|
||||||
|
- [GitLab-Registry](https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/packages)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pacman -Sy
|
||||||
|
FILENAME=/opt/fastapi-dls.pkg.tar.zst
|
||||||
|
url -o $FILENAME <download-url>
|
||||||
|
pacman -U --noconfirm fastapi-dls.pkg.tar.zst
|
||||||
|
```
|
||||||
|
|
||||||
|
Start with `systemctl start fastapi-dls.service` and enable autostart with `systemctl enable fastapi-dls.service`.
|
||||||
|
|
||||||
## Let's Encrypt Certificate
|
## Let's Encrypt Certificate
|
||||||
|
|
||||||
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.
|
||||||
@ -237,12 +281,12 @@ 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 |
|
||||||
| `LEASE_EXPIRE_DAYS` | `90` | Lease time in days |
|
| `LEASE_EXPIRE_DAYS` | `90` | Lease time in days |
|
||||||
| `DATABASE` | `sqlite:///db.sqlite` | See [official dataset docs](https://dataset.readthedocs.io/en/latest/quickstart.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) |
|
| `CORS_ORIGINS` | `https://{DLS_URL}` | Sets `Access-Control-Allow-Origin` header (comma separated string) |
|
||||||
| `SITE_KEY_XID` | `00000000-0000-0000-0000-000000000000` | Site identification uuid |
|
| `SITE_KEY_XID` | `00000000-0000-0000-0000-000000000000` | Site identification uuid |
|
||||||
| `INSTANCE_REF` | `00000000-0000-0000-0000-000000000000` | Instance identification uuid |
|
| `INSTANCE_REF` | `00000000-0000-0000-0000-000000000000` | Instance identification uuid |
|
||||||
@ -376,3 +420,10 @@ Dec 20 17:53:34 ubuntu-grid-server nvidia-gridd[10354]: License acquired success
|
|||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
# Credits
|
||||||
|
|
||||||
|
Thanks to vGPU community and all who uses this project and report bugs.
|
||||||
|
|
||||||
|
Special thanks to @samicrusader who created build file for ArchLinux.
|
||||||
|
|
||||||
|
150
app/main.py
150
app/main.py
@ -3,12 +3,11 @@ from base64 import b64encode as b64enc
|
|||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from os.path import join, dirname
|
from os.path import join, dirname
|
||||||
from os import getenv
|
from os import getenv as env
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from fastapi import FastAPI, HTTPException
|
from fastapi import FastAPI, HTTPException
|
||||||
from fastapi.requests import Request
|
from fastapi.requests import Request
|
||||||
from fastapi.encoders import jsonable_encoder
|
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
@ -16,56 +15,33 @@ from calendar import timegm
|
|||||||
from jose import jws, jwk, jwt
|
from jose import jws, jwk, jwt
|
||||||
from jose.constants import ALGORITHMS
|
from jose.constants import ALGORITHMS
|
||||||
from starlette.middleware.cors import CORSMiddleware
|
from starlette.middleware.cors import CORSMiddleware
|
||||||
from starlette.responses import StreamingResponse, JSONResponse, HTMLResponse
|
from starlette.responses import StreamingResponse, JSONResponse, HTMLResponse, Response, RedirectResponse
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
try:
|
from util import load_key, load_file
|
||||||
# Crypto | Cryptodome on Debian
|
from orm import Origin, Lease, init as db_init, migrate
|
||||||
from Crypto.PublicKey import RSA
|
|
||||||
from Crypto.PublicKey.RSA import RsaKey
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
from Cryptodome.PublicKey import RSA
|
|
||||||
from Cryptodome.PublicKey.RSA import RsaKey
|
|
||||||
from orm import Origin, Lease, init as db_init
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
load_dotenv('../version.env')
|
load_dotenv('../version.env')
|
||||||
|
|
||||||
VERSION, COMMIT, DEBUG = getenv('VERSION', 'unknown'), getenv('COMMIT', 'unknown'), bool(getenv('DEBUG', False))
|
VERSION, COMMIT, DEBUG = env('VERSION', 'unknown'), env('COMMIT', 'unknown'), bool(env('DEBUG', False))
|
||||||
|
|
||||||
|
config = 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)
|
||||||
|
db = create_engine(str(env('DATABASE', 'sqlite:///db.sqlite')))
|
||||||
|
db_init(db), migrate(db)
|
||||||
|
|
||||||
def load_file(filename) -> bytes:
|
DLS_URL = str(env('DLS_URL', 'localhost'))
|
||||||
with open(filename, 'rb') as file:
|
DLS_PORT = int(env('DLS_PORT', '443'))
|
||||||
content = file.read()
|
SITE_KEY_XID = str(env('SITE_KEY_XID', '00000000-0000-0000-0000-000000000000'))
|
||||||
return content
|
INSTANCE_REF = str(env('INSTANCE_REF', '00000000-0000-0000-0000-000000000000'))
|
||||||
|
INSTANCE_KEY_RSA = load_key(str(env('INSTANCE_KEY_RSA', join(dirname(__file__), 'cert/instance.private.pem'))))
|
||||||
|
INSTANCE_KEY_PUB = load_key(str(env('INSTANCE_KEY_PUB', join(dirname(__file__), 'cert/instance.public.pem'))))
|
||||||
def load_key(filename) -> RsaKey:
|
|
||||||
return RSA.import_key(extern_key=load_file(filename), passphrase=None)
|
|
||||||
|
|
||||||
|
|
||||||
# todo: initialize certificate (or should be done by user, and passed through "volumes"?)
|
|
||||||
|
|
||||||
__details = dict(
|
|
||||||
title='FastAPI-DLS',
|
|
||||||
description='Minimal Delegated License Service (DLS).',
|
|
||||||
version=VERSION,
|
|
||||||
)
|
|
||||||
|
|
||||||
app, db = FastAPI(**__details), create_engine(str(getenv('DATABASE', 'sqlite:///db.sqlite')))
|
|
||||||
db_init(db)
|
|
||||||
|
|
||||||
DLS_URL = str(getenv('DLS_URL', 'localhost'))
|
|
||||||
DLS_PORT = int(getenv('DLS_PORT', '443'))
|
|
||||||
SITE_KEY_XID = str(getenv('SITE_KEY_XID', '00000000-0000-0000-0000-000000000000'))
|
|
||||||
INSTANCE_REF = str(getenv('INSTANCE_REF', '00000000-0000-0000-0000-000000000000'))
|
|
||||||
INSTANCE_KEY_RSA = load_key(str(getenv('INSTANCE_KEY_RSA', join(dirname(__file__), 'cert/instance.private.pem'))))
|
|
||||||
INSTANCE_KEY_PUB = load_key(str(getenv('INSTANCE_KEY_PUB', join(dirname(__file__), 'cert/instance.public.pem'))))
|
|
||||||
TOKEN_EXPIRE_DELTA = relativedelta(hours=1) # days=1
|
TOKEN_EXPIRE_DELTA = relativedelta(hours=1) # days=1
|
||||||
LEASE_EXPIRE_DELTA = relativedelta(days=int(getenv('LEASE_EXPIRE_DAYS', 90)))
|
LEASE_EXPIRE_DELTA = relativedelta(days=int(env('LEASE_EXPIRE_DAYS', 90)))
|
||||||
|
|
||||||
CORS_ORIGINS = getenv('CORS_ORIGINS').split(',') if (getenv('CORS_ORIGINS')) else f'https://{DLS_URL}' # todo: prevent static https
|
CORS_ORIGINS = env('CORS_ORIGINS').split(',') if (env('CORS_ORIGINS')) else f'https://{DLS_URL}' # todo: prevent static https
|
||||||
|
|
||||||
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)
|
||||||
@ -88,36 +64,104 @@ def get_token(request: Request) -> dict:
|
|||||||
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})
|
||||||
|
|
||||||
|
|
||||||
@app.get('/')
|
@app.get('/', summary='* Index')
|
||||||
async def index():
|
async def index():
|
||||||
|
return RedirectResponse('/-/readme')
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/status', summary='* Status', description='Returns current service status, version (incl. git-commit) and some variables.', deprecated=True)
|
||||||
|
async def status(request: Request):
|
||||||
|
return JSONResponse({'status': 'up', 'version': VERSION, 'commit': COMMIT, 'debug': DEBUG})
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/-/health', summary='* Health')
|
||||||
|
async def _health(request: Request):
|
||||||
|
return JSONResponse({'status': 'up', 'version': VERSION, 'commit': COMMIT, 'debug': DEBUG})
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/-/readme', summary='* Readme')
|
||||||
|
async def _readme():
|
||||||
from markdown import markdown
|
from markdown import markdown
|
||||||
content = load_file('../README.md').decode('utf-8')
|
content = load_file('../README.md').decode('utf-8')
|
||||||
return HTMLResponse(markdown(text=content, extensions=['tables', 'fenced_code', 'md_in_html', 'nl2br', 'toc']))
|
return HTMLResponse(markdown(text=content, extensions=['tables', 'fenced_code', 'md_in_html', 'nl2br', 'toc']))
|
||||||
|
|
||||||
|
|
||||||
@app.get('/status')
|
@app.get('/-/manage', summary='* Management UI')
|
||||||
async def status(request: Request):
|
async def _manage(request: Request):
|
||||||
return JSONResponse({'status': 'up', 'version': VERSION, 'commit': COMMIT, 'debug': DEBUG})
|
response = '''
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>FastAPI-DLS Management</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<button onclick="deleteOrigins()">delete origins and their leases</button>
|
||||||
|
<button onclick="deleteLease()">delete specific lease</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function deleteOrigins() {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open("DELETE", '/-/origins', true);
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
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
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open("DELETE", `/-/lease/${lease_ref}`, true);
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
|
return HTMLResponse(response)
|
||||||
|
|
||||||
|
|
||||||
@app.get('/-/origins')
|
@app.get('/-/origins', summary='* Origins')
|
||||||
async def _origins(request: Request):
|
async def _origins(request: Request, leases: bool = False):
|
||||||
session = sessionmaker(bind=db)()
|
session = sessionmaker(bind=db)()
|
||||||
response = list(map(lambda x: jsonable_encoder(x), session.query(Origin).all()))
|
response = []
|
||||||
|
for origin in session.query(Origin).all():
|
||||||
|
x = origin.serialize()
|
||||||
|
if leases:
|
||||||
|
x['leases'] = list(map(lambda _: _.serialize(), Lease.find_by_origin_ref(db, origin.origin_ref)))
|
||||||
|
response.append(x)
|
||||||
session.close()
|
session.close()
|
||||||
return JSONResponse(response)
|
return JSONResponse(response)
|
||||||
|
|
||||||
|
|
||||||
@app.get('/-/leases')
|
@app.delete('/-/origins', summary='* Origins')
|
||||||
async def _leases(request: Request):
|
async def _origins_delete(request: Request):
|
||||||
|
Origin.delete(db)
|
||||||
|
return Response(status_code=201)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/-/leases', summary='* Leases')
|
||||||
|
async def _leases(request: Request, origin: bool = False):
|
||||||
session = sessionmaker(bind=db)()
|
session = sessionmaker(bind=db)()
|
||||||
response = list(map(lambda x: jsonable_encoder(x), session.query(Lease).all()))
|
response = []
|
||||||
|
for lease in session.query(Lease).all():
|
||||||
|
x = lease.serialize()
|
||||||
|
if origin:
|
||||||
|
# assume that each lease has a valid origin record
|
||||||
|
x['origin'] = session.query(Origin).filter(Origin.origin_ref == lease.origin_ref).first().serialize()
|
||||||
|
response.append(x)
|
||||||
session.close()
|
session.close()
|
||||||
return JSONResponse(response)
|
return JSONResponse(response)
|
||||||
|
|
||||||
|
|
||||||
|
@app.delete('/-/lease/{lease_ref}', summary='* Lease')
|
||||||
|
async def _lease_delete(request: Request, lease_ref: str):
|
||||||
|
if Lease.delete(db, lease_ref) == 1:
|
||||||
|
return Response(status_code=201)
|
||||||
|
raise HTTPException(status_code=404, detail='lease not found')
|
||||||
|
|
||||||
|
|
||||||
# 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')
|
@app.get('/client-token', summary='* Client-Token')
|
||||||
async def client_token():
|
async def client_token():
|
||||||
cur_time = datetime.utcnow()
|
cur_time = datetime.utcnow()
|
||||||
exp_time = cur_time + relativedelta(years=12)
|
exp_time = cur_time + relativedelta(years=12)
|
||||||
@ -130,7 +174,7 @@ async def client_token():
|
|||||||
"nbf": timegm(cur_time.timetuple()),
|
"nbf": timegm(cur_time.timetuple()),
|
||||||
"exp": timegm(exp_time.timetuple()),
|
"exp": timegm(exp_time.timetuple()),
|
||||||
"update_mode": "ABSOLUTE",
|
"update_mode": "ABSOLUTE",
|
||||||
"scope_ref_list": [str(uuid4())],
|
"scope_ref_list": [str(uuid4())], # this is our LEASE_REF
|
||||||
"fulfillment_class_ref_list": [],
|
"fulfillment_class_ref_list": [],
|
||||||
"service_instance_configuration": {
|
"service_instance_configuration": {
|
||||||
"nls_service_instance_ref": INSTANCE_REF,
|
"nls_service_instance_ref": INSTANCE_REF,
|
||||||
|
90
app/orm.py
90
app/orm.py
@ -1,6 +1,6 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from sqlalchemy import Column, VARCHAR, CHAR, ForeignKey, DATETIME, UniqueConstraint, update, and_, delete, inspect
|
from sqlalchemy import Column, VARCHAR, CHAR, ForeignKey, DATETIME, update, and_, inspect
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.engine import Engine
|
from sqlalchemy.engine import Engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
@ -21,6 +21,15 @@ class Origin(Base):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
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:
|
||||||
|
return {
|
||||||
|
'origin_ref': self.origin_ref,
|
||||||
|
'hostname': self.hostname,
|
||||||
|
'guest_driver_version': self.guest_driver_version,
|
||||||
|
'os_platform': self.os_platform,
|
||||||
|
'os_version': self.os_version,
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_statement(engine: Engine):
|
def create_statement(engine: Engine):
|
||||||
from sqlalchemy.schema import CreateTable
|
from sqlalchemy.schema import CreateTable
|
||||||
@ -28,29 +37,41 @@ class Origin(Base):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_or_update(engine: Engine, origin: "Origin"):
|
def create_or_update(engine: Engine, origin: "Origin"):
|
||||||
session = sessionmaker(autocommit=True, autoflush=True, bind=engine)()
|
session = sessionmaker(bind=engine)()
|
||||||
entity = session.query(Origin).filter(Origin.origin_ref == origin.origin_ref).first()
|
entity = session.query(Origin).filter(Origin.origin_ref == origin.origin_ref).first()
|
||||||
print(entity)
|
print(entity)
|
||||||
if entity is None:
|
if entity is None:
|
||||||
session.add(origin)
|
session.add(origin)
|
||||||
else:
|
else:
|
||||||
values = dict(
|
x = dict(
|
||||||
hostname=origin.hostname,
|
hostname=origin.hostname,
|
||||||
guest_driver_version=origin.guest_driver_version,
|
guest_driver_version=origin.guest_driver_version,
|
||||||
os_platform=origin.os_platform,
|
os_platform=origin.os_platform,
|
||||||
os_version=origin.os_version,
|
os_version=origin.os_version
|
||||||
)
|
)
|
||||||
session.execute(update(Origin).where(Origin.origin_ref == origin.origin_ref).values(**values))
|
session.execute(update(Origin).where(Origin.origin_ref == origin.origin_ref).values(**x))
|
||||||
|
session.commit()
|
||||||
session.flush()
|
session.flush()
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def delete(engine: Engine, origins: ["Origin"] = None) -> int:
|
||||||
|
session = sessionmaker(bind=engine)()
|
||||||
|
if origins is None:
|
||||||
|
deletions = session.query(Origin).delete()
|
||||||
|
else:
|
||||||
|
deletions = session.query(Origin).filter(Origin.origin_ref in origins).delete()
|
||||||
|
session.commit()
|
||||||
|
session.close()
|
||||||
|
return deletions
|
||||||
|
|
||||||
|
|
||||||
class Lease(Base):
|
class Lease(Base):
|
||||||
__tablename__ = "lease"
|
__tablename__ = "lease"
|
||||||
|
|
||||||
origin_ref = Column(CHAR(length=36), ForeignKey(Origin.origin_ref), primary_key=True, nullable=False, index=True) # uuid4
|
|
||||||
lease_ref = Column(CHAR(length=36), primary_key=True, nullable=False, index=True) # uuid4
|
lease_ref = Column(CHAR(length=36), primary_key=True, nullable=False, index=True) # uuid4
|
||||||
|
|
||||||
|
origin_ref = Column(CHAR(length=36), ForeignKey(Origin.origin_ref, ondelete='CASCADE'), nullable=False, index=True) # uuid4
|
||||||
lease_created = Column(DATETIME(), nullable=False)
|
lease_created = Column(DATETIME(), nullable=False)
|
||||||
lease_expires = Column(DATETIME(), nullable=False)
|
lease_expires = Column(DATETIME(), nullable=False)
|
||||||
lease_updated = Column(DATETIME(), nullable=False)
|
lease_updated = Column(DATETIME(), nullable=False)
|
||||||
@ -58,6 +79,15 @@ class Lease(Base):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'Lease(origin_ref={self.origin_ref}, lease_ref={self.lease_ref}, expires={self.lease_expires})'
|
return f'Lease(origin_ref={self.origin_ref}, lease_ref={self.lease_ref}, expires={self.lease_expires})'
|
||||||
|
|
||||||
|
def serialize(self) -> dict:
|
||||||
|
return {
|
||||||
|
'lease_ref': self.lease_ref,
|
||||||
|
'origin_ref': self.origin_ref,
|
||||||
|
'lease_created': self.lease_created.isoformat(),
|
||||||
|
'lease_expires': self.lease_expires.isoformat(),
|
||||||
|
'lease_updated': self.lease_updated.isoformat(),
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_statement(engine: Engine):
|
def create_statement(engine: Engine):
|
||||||
from sqlalchemy.schema import CreateTable
|
from sqlalchemy.schema import CreateTable
|
||||||
@ -65,43 +95,54 @@ class Lease(Base):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_or_update(engine: Engine, lease: "Lease"):
|
def create_or_update(engine: Engine, lease: "Lease"):
|
||||||
session = sessionmaker(autocommit=True, autoflush=True, bind=engine)()
|
session = sessionmaker(bind=engine)()
|
||||||
entity = session.query(Lease).filter(and_(Lease.origin_ref == lease.origin_ref, Lease.lease_ref == lease.lease_ref)).first()
|
entity = session.query(Lease).filter(Lease.lease_ref == lease.lease_ref).first()
|
||||||
if entity is None:
|
if entity is None:
|
||||||
if lease.lease_updated is None:
|
if lease.lease_updated is None:
|
||||||
lease.lease_updated = lease.lease_created
|
lease.lease_updated = lease.lease_created
|
||||||
session.add(lease)
|
session.add(lease)
|
||||||
else:
|
else:
|
||||||
values = dict(lease_expires=lease.lease_expires, lease_updated=lease.lease_updated)
|
x = dict(origin_ref=lease.origin_ref, lease_expires=lease.lease_expires, lease_updated=lease.lease_updated)
|
||||||
session.execute(update(Lease).where(and_(Lease.origin_ref == lease.origin_ref, Lease.lease_ref == lease.lease_ref)).values(**values))
|
session.execute(update(Lease).where(Lease.lease_ref == lease.lease_ref).values(**x))
|
||||||
|
session.commit()
|
||||||
session.flush()
|
session.flush()
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_by_origin_ref(engine: Engine, origin_ref: str) -> ["Lease"]:
|
def find_by_origin_ref(engine: Engine, origin_ref: str) -> ["Lease"]:
|
||||||
session = sessionmaker(autocommit=True, autoflush=True, bind=engine)()
|
session = sessionmaker(bind=engine)()
|
||||||
entities = session.query(Lease).filter(Lease.origin_ref == origin_ref).all()
|
entities = session.query(Lease).filter(Lease.origin_ref == origin_ref).all()
|
||||||
session.close()
|
session.close()
|
||||||
return entities
|
return entities
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_by_origin_ref_and_lease_ref(engine: Engine, origin_ref: str, lease_ref: str) -> "Lease":
|
def find_by_origin_ref_and_lease_ref(engine: Engine, origin_ref: str, lease_ref: str) -> "Lease":
|
||||||
session = sessionmaker(autocommit=True, autoflush=True, bind=engine)()
|
session = sessionmaker(bind=engine)()
|
||||||
entity = session.query(Lease).filter(and_(Lease.origin_ref == origin_ref, Lease.lease_ref == lease_ref)).first()
|
entity = session.query(Lease).filter(and_(Lease.origin_ref == origin_ref, Lease.lease_ref == lease_ref)).first()
|
||||||
session.close()
|
session.close()
|
||||||
return entity
|
return entity
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def renew(engine: Engine, lease: "Lease", lease_expires: datetime.datetime, lease_updated: datetime.datetime):
|
def renew(engine: Engine, lease: "Lease", lease_expires: datetime.datetime, lease_updated: datetime.datetime):
|
||||||
session = sessionmaker(autocommit=True, autoflush=True, bind=engine)()
|
session = sessionmaker(bind=engine)()
|
||||||
values = dict(lease_expires=lease.lease_expires, lease_updated=lease.lease_updated)
|
x = dict(lease_expires=lease.lease_expires, lease_updated=lease.lease_updated)
|
||||||
session.execute(update(Lease).where(and_(Lease.origin_ref == lease.origin_ref, Lease.lease_ref == lease.lease_ref)).values(**values))
|
session.execute(update(Lease).where(and_(Lease.origin_ref == lease.origin_ref, Lease.lease_ref == lease.lease_ref)).values(**x))
|
||||||
|
session.commit()
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cleanup(engine: Engine, origin_ref: str) -> int:
|
def cleanup(engine: Engine, origin_ref: str) -> int:
|
||||||
session = sessionmaker(autocommit=True, autoflush=True, bind=engine)()
|
session = sessionmaker(bind=engine)()
|
||||||
deletions = session.query(Lease).filter(Lease.origin_ref == origin_ref).delete()
|
deletions = session.query(Lease).filter(Lease.origin_ref == origin_ref).delete()
|
||||||
|
session.commit()
|
||||||
|
session.close()
|
||||||
|
return deletions
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def delete(engine: Engine, lease_ref: str) -> int:
|
||||||
|
session = sessionmaker(bind=engine)()
|
||||||
|
deletions = session.query(Lease).filter(Lease.lease_ref == lease_ref).delete()
|
||||||
|
session.commit()
|
||||||
session.close()
|
session.close()
|
||||||
return deletions
|
return deletions
|
||||||
|
|
||||||
@ -113,4 +154,21 @@ def init(engine: Engine):
|
|||||||
for table in tables:
|
for table in tables:
|
||||||
if not db.dialect.has_table(engine.connect(), table.__tablename__):
|
if not db.dialect.has_table(engine.connect(), table.__tablename__):
|
||||||
session.execute(str(table.create_statement(engine)))
|
session.execute(str(table.create_statement(engine)))
|
||||||
|
session.commit()
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
|
||||||
|
def migrate(engine: Engine):
|
||||||
|
db = inspect(engine)
|
||||||
|
|
||||||
|
def upgrade_1_0_to_1_1():
|
||||||
|
x = db.dialect.get_columns(engine.connect(), Lease.__tablename__)
|
||||||
|
x = next(_ for _ in x if _['name'] == 'origin_ref')
|
||||||
|
if x['primary_key'] > 0:
|
||||||
|
print('Found old database schema with "origin_ref" as primary-key in "lease" table. Dropping table!')
|
||||||
|
print(' Your leases are recreated on next renewal!')
|
||||||
|
print(' If an error message appears on the client, you can ignore it.')
|
||||||
|
Lease.__table__.drop(bind=engine)
|
||||||
|
init(engine)
|
||||||
|
|
||||||
|
upgrade_1_0_to_1_1()
|
||||||
|
21
app/util.py
Normal file
21
app/util.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
try:
|
||||||
|
# Crypto | Cryptodome on Debian
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Crypto.PublicKey.RSA import RsaKey
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
from Cryptodome.PublicKey import RSA
|
||||||
|
from Cryptodome.PublicKey.RSA import RsaKey
|
||||||
|
|
||||||
|
|
||||||
|
def load_file(filename) -> bytes:
|
||||||
|
with open(filename, 'rb') as file:
|
||||||
|
content = file.read()
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def load_key(filename) -> RsaKey:
|
||||||
|
return RSA.import_key(extern_key=load_file(filename), passphrase=None)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_key() -> RsaKey:
|
||||||
|
return RSA.generate(bits=2048)
|
147
test/main.py
147
test/main.py
@ -1,6 +1,13 @@
|
|||||||
|
from base64 import b64encode as b64enc
|
||||||
|
from hashlib import sha256
|
||||||
|
from calendar import timegm
|
||||||
|
from datetime import datetime
|
||||||
|
from os.path import dirname, join
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from jose import jwt
|
from dateutil.relativedelta import relativedelta
|
||||||
|
from jose import jwt, jwk
|
||||||
|
from jose.constants import ALGORITHMS
|
||||||
from starlette.testclient import TestClient
|
from starlette.testclient import TestClient
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@ -9,10 +16,21 @@ sys.path.append('../')
|
|||||||
sys.path.append('../app')
|
sys.path.append('../app')
|
||||||
|
|
||||||
from app import main
|
from app import main
|
||||||
|
from app.util import generate_key, load_key
|
||||||
|
|
||||||
client = TestClient(main.app)
|
client = TestClient(main.app)
|
||||||
|
|
||||||
ORIGIN_REF = str(uuid4())
|
ORIGIN_REF, LEASE_REF = str(uuid4()), str(uuid4())
|
||||||
|
SECRET = "HelloWorld"
|
||||||
|
|
||||||
|
# INSTANCE_KEY_RSA = generate_key()
|
||||||
|
# INSTANCE_KEY_PUB = INSTANCE_KEY_RSA.public_key()
|
||||||
|
|
||||||
|
INSTANCE_KEY_RSA = load_key(str(join(dirname(__file__), '../app/cert/instance.private.pem')))
|
||||||
|
INSTANCE_KEY_PUB = load_key(str(join(dirname(__file__), '../app/cert/instance.public.pem')))
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
def test_index():
|
def test_index():
|
||||||
@ -26,11 +44,43 @@ def test_status():
|
|||||||
assert response.json()['status'] == 'up'
|
assert response.json()['status'] == 'up'
|
||||||
|
|
||||||
|
|
||||||
|
def test_health():
|
||||||
|
response = client.get('/-/health')
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json()['status'] == 'up'
|
||||||
|
|
||||||
|
|
||||||
|
def test_readme():
|
||||||
|
response = client.get('/-/readme')
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_manage():
|
||||||
|
response = client.get('/-/manage')
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_client_token():
|
def test_client_token():
|
||||||
response = client.get('/client-token')
|
response = client.get('/client-token')
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_origins():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_origins_delete():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_leases():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_lease_delete():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_auth_v1_origin():
|
def test_auth_v1_origin():
|
||||||
payload = {
|
payload = {
|
||||||
"registration_pending": False,
|
"registration_pending": False,
|
||||||
@ -52,9 +102,30 @@ def test_auth_v1_origin():
|
|||||||
assert response.json()['origin_ref'] == ORIGIN_REF
|
assert response.json()['origin_ref'] == ORIGIN_REF
|
||||||
|
|
||||||
|
|
||||||
|
def auth_v1_origin_update():
|
||||||
|
payload = {
|
||||||
|
"registration_pending": False,
|
||||||
|
"environment": {
|
||||||
|
"guest_driver_version": "guest_driver_version",
|
||||||
|
"hostname": "myhost",
|
||||||
|
"ip_address_list": ["192.168.1.123"],
|
||||||
|
"os_version": "os_version",
|
||||||
|
"os_platform": "os_platform",
|
||||||
|
"fingerprint": {"mac_address_list": ["ff:ff:ff:ff:ff:ff"]},
|
||||||
|
"host_driver_version": "host_driver_version"
|
||||||
|
},
|
||||||
|
"update_pending": False,
|
||||||
|
"candidate_origin_ref": ORIGIN_REF,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post('/auth/v1/origin/update', json=payload)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json()['origin_ref'] == ORIGIN_REF
|
||||||
|
|
||||||
|
|
||||||
def test_auth_v1_code():
|
def test_auth_v1_code():
|
||||||
payload = {
|
payload = {
|
||||||
"code_challenge": "0wmaiAMAlTIDyz4Fgt2/j0tXnGv72TYbbLs4ISRCZlY",
|
"code_challenge": b64enc(sha256(SECRET.encode('utf-8')).digest()).rstrip(b'=').decode('utf-8'),
|
||||||
"origin_ref": ORIGIN_REF,
|
"origin_ref": ORIGIN_REF,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,20 +137,80 @@ def test_auth_v1_code():
|
|||||||
|
|
||||||
|
|
||||||
def test_auth_v1_token():
|
def test_auth_v1_token():
|
||||||
pass
|
cur_time = datetime.utcnow()
|
||||||
|
access_expires_on = cur_time + relativedelta(hours=1)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"iat": timegm(cur_time.timetuple()),
|
||||||
|
"exp": timegm(access_expires_on.timetuple()),
|
||||||
|
"challenge": b64enc(sha256(SECRET.encode('utf-8')).digest()).rstrip(b'=').decode('utf-8'),
|
||||||
|
"origin_ref": ORIGIN_REF,
|
||||||
|
"key_ref": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"kid": "00000000-0000-0000-0000-000000000000"
|
||||||
|
}
|
||||||
|
payload = {
|
||||||
|
"auth_code": jwt.encode(payload, key=jwt_encode_key, headers={'kid': payload.get('kid')},
|
||||||
|
algorithm=ALGORITHMS.RS256),
|
||||||
|
"code_verifier": SECRET,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = client.post('/auth/v1/token', json=payload)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
token = response.json()['auth_token']
|
||||||
|
payload = jwt.decode(token=token, key=jwt_decode_key, algorithms=ALGORITHMS.RS256, options={'verify_aud': False})
|
||||||
|
assert payload['origin_ref'] == ORIGIN_REF
|
||||||
|
|
||||||
|
|
||||||
def test_leasing_v1_lessor():
|
def test_leasing_v1_lessor():
|
||||||
pass
|
payload = {
|
||||||
|
'fulfillment_context': {
|
||||||
|
'fulfillment_class_ref_list': []
|
||||||
|
},
|
||||||
|
'lease_proposal_list': [{
|
||||||
|
'license_type_qualifiers': {'count': 1},
|
||||||
|
'product': {'name': 'NVIDIA RTX Virtual Workstation'}
|
||||||
|
}],
|
||||||
|
'proposal_evaluation_mode': 'ALL_OF',
|
||||||
|
'scope_ref_list': [LEASE_REF]
|
||||||
|
}
|
||||||
|
|
||||||
|
bearer_token = jwt.encode({"origin_ref": ORIGIN_REF}, key=jwt_encode_key, algorithm=ALGORITHMS.RS256)
|
||||||
|
bearer_token = f'Bearer {bearer_token}'
|
||||||
|
response = client.post('/leasing/v1/lessor', json=payload, headers={'authorization': bearer_token})
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
lease_result_list = response.json()['lease_result_list']
|
||||||
|
assert len(lease_result_list) == 1
|
||||||
|
assert lease_result_list[0]['lease']['ref'] == LEASE_REF
|
||||||
|
|
||||||
|
|
||||||
def test_leasing_v1_lessor_lease():
|
def test_leasing_v1_lessor_lease():
|
||||||
pass
|
bearer_token = jwt.encode({"origin_ref": ORIGIN_REF}, key=jwt_encode_key, algorithm=ALGORITHMS.RS256)
|
||||||
|
bearer_token = f'Bearer {bearer_token}'
|
||||||
|
response = client.get('/leasing/v1/lessor/leases', headers={'authorization': bearer_token})
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
active_lease_list = response.json()['active_lease_list']
|
||||||
|
assert len(active_lease_list) == 1
|
||||||
|
assert active_lease_list[0] == LEASE_REF
|
||||||
|
|
||||||
|
|
||||||
def test_leasing_v1_lease_renew():
|
def test_leasing_v1_lease_renew():
|
||||||
pass
|
bearer_token = jwt.encode({"origin_ref": ORIGIN_REF}, key=jwt_encode_key, algorithm=ALGORITHMS.RS256)
|
||||||
|
bearer_token = f'Bearer {bearer_token}'
|
||||||
|
response = client.put(f'/leasing/v1/lease/{LEASE_REF}', headers={'authorization': bearer_token})
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
assert response.json()['lease_ref'] == LEASE_REF
|
||||||
|
|
||||||
|
|
||||||
def test_leasing_v1_lessor_lease_remove():
|
def test_leasing_v1_lessor_lease_remove():
|
||||||
pass
|
bearer_token = jwt.encode({"origin_ref": ORIGIN_REF}, key=jwt_encode_key, algorithm=ALGORITHMS.RS256)
|
||||||
|
bearer_token = f'Bearer {bearer_token}'
|
||||||
|
response = client.delete('/leasing/v1/lessor/leases', headers={'authorization': bearer_token})
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
released_lease_list = response.json()['released_lease_list']
|
||||||
|
assert len(released_lease_list) == 1
|
||||||
|
assert released_lease_list[0] == LEASE_REF
|
||||||
|
@ -1 +1 @@
|
|||||||
VERSION=1.0.0
|
VERSION=1.1
|
||||||
|
Loading…
Reference in New Issue
Block a user