diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..4dce538 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,173 @@ +name: Deploy Image + +env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + IMAGE_NAME: ${{ vars.IMAGE_NAME || 'quickstart-test' }} + PREFIXED_IMAGE: ${{ format('blueos-{0}', vars.IMAGE_NAME || 'quickstart-test') }} + # Target the same platforms as BlueOS by default + PLATFORMS: ${{ vars.BUILD_PLATFORMS || 'linux/arm/v7,linux/arm64/v8,linux/amd64' }} + +on: + # Run manually + workflow_dispatch: + # NOTE: caches may be removed if not run weekly + # -> may be worth scheduling for every 6 days + +jobs: + deploy-docker-image: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 #Number of commits to fetch. 0 indicates all history for all branches and tags + + - name: Prepare + id: prepare + run: | + # Deploy image with the name of the branch, if the build is a git tag replace tag with the tag name. + # If the git tag is in SemVer format, append the "latest" tag to the image + DOCKER_IMAGE=${DOCKER_USERNAME}/${PREFIXED_IMAGE} + VERSION=${GITHUB_REF##*/} + + if [[ $GITHUB_REF == refs/tags/* ]]; then + VERSION=${GITHUB_REF#refs/tags/} + fi + + TAGS="--tag ${DOCKER_IMAGE}:${VERSION}" + if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then + TAGS="$TAGS --tag ${DOCKER_IMAGE}:latest" + fi + + echo "docker_image=${DOCKER_IMAGE}" >> $GITHUB_OUTPUT + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "buildx_args=\ + --build-arg IMAGE_NAME=${IMAGE_NAME} \ + --build-arg AUTHOR='${{ vars.MY_NAME || 'Author Name' }}' \ + --build-arg AUTHOR_EMAIL='${{ vars.MY_EMAIL || 'author.email@example.com' }}' \ + --build-arg MAINTAINER='${{ vars.ORG_NAME || github.repository_owner }}' \ + --build-arg MAINTAINER_EMAIL='${{ vars.ORG_EMAIL || 'maintainer.email@example.com' }}' \ + --build-arg REPO='${{ github.repository }}' \ + --build-arg OWNER='${{ github.repository_owner }}' \ + --cache-from 'type=local,src=/tmp/.buildx-cache' \ + --cache-to 'type=local,dest=/tmp/.buildx-cache' \ + ${TAGS} \ + --file Dockerfile ." >> $GITHUB_OUTPUT + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + with: + platforms: all + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + with: + version: latest + + - name: Cache Docker layers + uses: actions/cache@v3 + id: cache + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${IMAGE_NAME}-${{ hashFiles('Dockerfile') }} + restore-keys: | + ${{ runner.os }}-buildx-${IMAGE_NAME}-${{ hashFiles('Dockerfile') }} + ${{ runner.os }}-buildx-${IMAGE_NAME} + + - name: Docker Buildx (build) + run: | + # Pull latest development version of image (from main/master branch) to help with build speed + for platform in $(echo ${PLATFORMS} | tr ',' '\n'); do + docker pull --platform ${platform} \ + ${DOCKER_USERNAME}/${PREFIXED_IMAGE}:${{ github.event.repository.default_branch }} || true + done + docker buildx build \ + --output "type=image,push=false" \ + --platform ${PLATFORMS} \ + ${{ steps.prepare.outputs.buildx_args }} + + - name: Login to DockerHub + if: success() + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Docker Buildx (push) + if: success() + run: | + docker buildx build \ + --output "type=image,push=true" \ + --platform ${PLATFORMS} \ + ${{ steps.prepare.outputs.buildx_args }} + + # Sanity check - if inspection fails something has gone very wrong + - name: Inspect image + run: | + docker buildx imagetools \ + inspect ${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.version }} + + - name: Create image artifact + if: success() + run: | + DOCKER_IMAGE=${DOCKER_USERNAME}/${PREFIXED_IMAGE} + GIT_HASH_SHORT=$(git rev-parse --short "$GITHUB_SHA") + docker buildx build \ + ${{ steps.prepare.outputs.buildx_args }} \ + --platform "linux/arm64/v8" \ + --tag ${DOCKER_IMAGE}:${GIT_HASH_SHORT} \ + --output "type=docker,dest=${PREFIXED_IMAGE}-docker-image-${GIT_HASH_SHORT}-arm64-v8.tar" \ + + - name: Upload artifact arm64-v8 + uses: actions/upload-artifact@v3 + if: success() + with: + name: ${{ env.PREFIXED_IMAGE }}-docker-image-arm64-v8 + path: '*arm64-v8.tar' + + - name: Create image artifact + if: success() + run: | + DOCKER_IMAGE=${DOCKER_USERNAME}/${PREFIXED_IMAGE} + GIT_HASH_SHORT=$(git rev-parse --short "$GITHUB_SHA") + docker buildx build \ + ${{ steps.prepare.outputs.buildx_args }} \ + --platform "linux/arm/v7" \ + --tag ${DOCKER_IMAGE}:${GIT_HASH_SHORT} \ + --output "type=docker,dest=${PREFIXED_IMAGE}-docker-image-${GIT_HASH_SHORT}-arm-v7.tar" \ + + - name: Upload artifact arm-v7 + uses: actions/upload-artifact@v3 + if: success() + with: + name: ${{ env.PREFIXED_IMAGE }}-docker-image-arm-v7 + path: '*arm-v7.tar' + + - name: Create image artifact + if: success() + run: | + DOCKER_IMAGE=${DOCKER_USERNAME}/${PREFIXED_IMAGE} + GIT_HASH_SHORT=$(git rev-parse --short "$GITHUB_SHA") + docker buildx build \ + ${{ steps.prepare.outputs.buildx_args }} \ + --platform "linux/amd64" \ + --tag ${DOCKER_IMAGE}:${GIT_HASH_SHORT} \ + --output "type=docker,dest=${PREFIXED_IMAGE}-docker-image-${GIT_HASH_SHORT}-amd64.tar" \ + + - name: Upload artifact amd64 + uses: actions/upload-artifact@v3 + if: success() + with: + name: ${{ env.PREFIXED_IMAGE }}-docker-image-amd64 + path: '*amd64.tar' + + - name: Upload docker image for release + uses: svenstaro/upload-release-action@v2 + if: startsWith(github.ref, 'refs/tags') && success() + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: '*.tar' + tag: ${{ github.ref }} + overwrite: true + prerelease: true + file_glob: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c623bc1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.venv +*.swp +*.egg-info +**/build +**/__pycache__ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..293bbfb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,59 @@ +FROM python:3.11-slim + +# RUN apt-get update && \ +# apt-get -y install gcc && \ +# rm -rf /var/lib/apt/lists/* + +COPY app /app +RUN python -m pip install /app --extra-index-url https://www.piwheels.org/simple + +EXPOSE 8000/tcp + +LABEL version="0.0.1" + +ARG IMAGE_NAME + +LABEL permissions='\ +{\ + "ExposedPorts": {\ + "8000/tcp": {}\ + },\ + "HostConfig": {\ + "Binds":["/root/.config/blueos/extensions/$IMAGE_NAME:/root/.config"],\ + "ExtraHosts": ["host.docker.internal:host-gateway"],\ + "PortBindings": {\ + "8000/tcp": [\ + {\ + "HostPort": ""\ + }\ + ]\ + }\ + }\ +}' + +ARG AUTHOR +ARG AUTHOR_EMAIL +LABEL authors='[\ + {\ + "name": "$AUTHOR",\ + "email": "$AUTHOR_EMAIL"\ + }\ +]' + +ARG MAINTAINER +ARG MAINTAINER_EMAIL +LABEL company='{\ + "about": "",\ + "name": "$MAINTAINER",\ + "email": "$MAINTAINER_EMAIL"\ + }' +LABEL type="example" +ARG REPO +ARG OWNER +LABEL readme='https://raw.githubusercontent.com/$OWNER/$REPO/{tag}/README.md' +LABEL links='{\ + "source": "https://github.com/$OWNER/$REPO"\ + }' +LABEL requirements="core >= 1.1" + +ENTRYPOINT litestar run --host 0.0.0.0 diff --git a/app/index.html b/app/index.html new file mode 100644 index 0000000..ad526fc --- /dev/null +++ b/app/index.html @@ -0,0 +1,57 @@ + + + QuickStart + + + +

Quick Start Functionalities

+ +

Clicks: 0

+ +

Clicks: ?

+ +

Clicks: 0

+ +

Clicks: ?

+ + diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..dad2fc3 --- /dev/null +++ b/app/main.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +from pathlib import Path +import requests +from litestar import Litestar, get, MediaType +from litestar.controller import Controller +from litestar.datastructures import State + +class CountController(Controller): + COUNT_VAR = 'quickstart_backend_perm_count' + def __init__(self, *args, **kwargs): + self._temp_count = -1 + super().__init__(*args, **kwargs) + + @get("/", media_type=MediaType.HTML) + def root() -> str: + return Path('index.html').read_text() + + @get("/temp_count") + def increment_temp_count(self) -> dict[str, int]: + self._temp_count += 1 + return {"value": self._temp_count} + + @get("/persistent_count") + def increment_persistent_count(self, state: State) -> dict[str, int]: + # read the existing persistent count value (from the BlueOS "Bag of Holding" service API) + try: + response = requests.get(f'{state.bag_url}/get/{self.COUNT_VAR}') + response.raise_for_status() + value = response.json()['value'] + except Exception: # TODO: specifically except HTTP error 400 (using response.status_code?) + value = 0 + value += 1 + # write the incremented value back out + output = {'value': value} + requests.post(f'{state.bag_url}/set/{self.COUNT_VAR}', data=output) + return output + + +import logging +#from argparse import ArgumentParser + +#parser = ArgumentParser() +#parser.add_argument('--log_path', type=Path, default='/root/.config/logs/') +#parser.add_argument('--bag_url', default='http://host.docker.internal:9101') +#args = parser.parse_args() + +logging.basicConfig( + format='%(asctime)s: %(message)s', level=logging.INFO +) + +logger = logging.getLogger(__name__) +# TODO: create file logger handler - ideally rotating + +#def set_state_on_startup(app: Litestar) -> None: +# app.state.bag_url = args.bag_url + +app = Litestar( + route_handlers=[CountController], + state=State({'bag_url':'http://host.docker.internal:9101/v1.0'}), + #on_startup=[set_state_on_startup], +) diff --git a/app/pyproject.toml b/app/pyproject.toml new file mode 100644 index 0000000..2fb2168 --- /dev/null +++ b/app/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "quickstart" +version = "0.0.1" +description = "BlueOS QuickStart Example Extension" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] +dependencies = [ + "requests", + "litestar[standard]", +] +