Skip to content

Commit

Permalink
add initial application and workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
ES-Alexander committed Oct 3, 2023
1 parent f356046 commit 8cb2d57
Show file tree
Hide file tree
Showing 7 changed files with 379 additions and 0 deletions.
173 changes: 173 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -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 || '[email protected]' }}' \
--build-arg MAINTAINER='${{ vars.ORG_NAME || github.repository_owner }}' \
--build-arg MAINTAINER_EMAIL='${{ vars.ORG_EMAIL || '[email protected]' }}' \
--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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.venv
*.swp
*.egg-info
**/build
**/__pycache__
59 changes: 59 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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
62 changes: 62 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -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
from litestar.static_files.config import StaticFilesConfig

class CountController(Controller):
COUNT_VAR = 'quickstart_backend_perm_count'
def __init__(self, *args, **kwargs):
self._temp_count = -1
super().__init__(*args, **kwargs)

@get("/temp_count", sync_to_thread=False)
def increment_temp_count(self) -> dict[str, int]:
self._temp_count += 1
return {"value": self._temp_count}

@get("/persistent_count", sync_to_thread=True)
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'}),
static_files_config=[
StaticFilesConfig(directories=['app/static'], path='/', html_mode=True)
],
#on_startup=[set_state_on_startup],
)
14 changes: 14 additions & 0 deletions app/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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]",
]

57 changes: 57 additions & 0 deletions app/static/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<html>
<head>
<title>QuickStart</title>
<script type="text/javascript" defer>
// define persistent state getters
async function frontend_perm_load() {
fetch("http://host.docker.internal:9101/v1.0/get/quickstart_frontend_perm_count")
.then((response) => {
if (response.status === 400) {
return 0;
}
return response.json()["value"];
});
};

var frontend_count = 0;
function frontend_temp_click() {
frontend_count += 1;
document.getElementById("frontend_temp").textContent = frontend_count;
};
async function frontend_perm_click() {
var count = await frontend_perm_load();
count += 1;
fetch("http://host.docker.internal:9101/v1.0/set/quickstart_frontend_perm_count/", {
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
value: count
})
});
};
async function backend_temp_click() {
document.getElementById("backend_temp").textContent = (await (await fetch("/temp_count")).json())["value"];
};
async function backend_perm_click() {
document.getElementById("backend_perm").textContent = (await (await fetch("/persistent_count")).json())["value"];
};

// load initial persistent states
document.getElementById("frontend_perm").textContent = frontend_perm_load();
</script>
</head>
<body>
<h1>Quick Start Functionalities</h1>
<button type="button" onclick="frontend_temp_click()">Frontend Temporary</button>
<p>Clicks: <a id="frontend_temp">0</a></p>
<button type="button" onclick="frontend_perm_click()">Frontend Permanent</button>
<p>Clicks: <a id="frontend_perm">?</a></p>
<button type="button" onclick="backend_temp_click()">Backend Temporary</button>
<p>Clicks: <a id="backend_temp">0</a></p>
<button type="button" onclick="backend_perm_click()">Backend Permanent</button>
<p>Clicks: <a id="backend_perm">?</a></p>
</body>
</html>
9 changes: 9 additions & 0 deletions app/static/register_service
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name":"BlueOS QuickStart",
"description":"Example Extension",
"icon":"mdi-clock",
"company":"Blue Robotics",
"version":"0.0.1",
"webpage":"https://example.com",
"api":""
}

0 comments on commit 8cb2d57

Please sign in to comment.