-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add initial application and workflow
- Loading branch information
1 parent
f356046
commit 1f4234e
Showing
6 changed files
with
370 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.venv | ||
*.swp | ||
*.egg-info | ||
**/build | ||
**/__pycache__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 80/tcp | ||
|
||
LABEL version="0.0.1" | ||
|
||
ARG IMAGE_NAME | ||
|
||
LABEL permissions='\ | ||
{\ | ||
"ExposedPorts": {\ | ||
"80/tcp": {}\ | ||
},\ | ||
"HostConfig": {\ | ||
"Binds":["/root/.config/blueos/extensions/$IMAGE_NAME:/root/.config"],\ | ||
"ExtraHosts": ["host.docker.internal:host-gateway"],\ | ||
"PortBindings": {\ | ||
"80/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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() { | ||
return await fetch("http://localhost/temp_count").json()["value"]; | ||
}; | ||
async function backend_perm_click() { | ||
return await fetch("http://localhost/persistent_count").json()["value"]; | ||
}; | ||
|
||
// load initial persistent states | ||
document.getElementByID("frontend_perm").textContent = await 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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
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], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]", | ||
] | ||
|