Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initial commit of docker desktop extension #218

Merged
merged 18 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docker/dd-extension/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ui/node_modules
2 changes: 2 additions & 0 deletions docker/dd-extension/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
ui/build
3 changes: 3 additions & 0 deletions docker/dd-extension/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"makefile.configureOnOpen": false
}
47 changes: 47 additions & 0 deletions docker/dd-extension/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
FROM --platform=$BUILDPLATFORM node:21.6-alpine3.18 AS client-builder
WORKDIR /ui
# cache packages in layer
COPY ui/package.json /ui/package.json
COPY ui/package-lock.json /ui/package-lock.json
RUN --mount=type=cache,target=/usr/src/app/.npm \
npm set cache /usr/src/app/.npm && \
npm ci
# install
COPY ui /ui
RUN npm run build


FROM python:3.11-slim
ENV PYTHONUNBUFFERED=1

RUN apt-get update\
&& apt-get install --no-install-recommends -y curl \
&& rm -rf /var/lib/apt/lists/*\
&& pip install --upgrade pip

COPY docker-compose.yaml .
COPY metadata.json .
COPY logo.svg .

# Copy python dependecies file
COPY ./backend/requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r /app/requirements.txt
# Copy source code
COPY ./backend /app/

LABEL org.opencontainers.image.title="Plate Recognizer Installer" \
org.opencontainers.image.description="Plate Recognizer Installer for Stream and Snapshot SDKs" \
org.opencontainers.image.vendor="Plate Recognizer" \
com.docker.desktop.extension.api.version="0.3.4" \
com.docker.extension.screenshots="" \
com.docker.desktop.extension.icon="https://platerecognizer.com/wp-content/uploads/2020/07/plate-recognizer-alpr-anpr-logo.png" \
com.docker.extension.detailed-description="Select correct docker image and generate command for installing, updating or uninstalling Snapshot or Stream SDK" \
com.docker.extension.publisher-url="https://platerecognizer.com/" \
com.docker.extension.additional-urls="https://parkpow.com/" \
com.docker.extension.categories="utility-tools" \
com.docker.extension.changelog="Initial Release" \
com.docker.extension.account-info="required"


COPY --from=client-builder /ui/build ui
CMD ["gunicorn", "--workers", "1", "--chdir", "/app", "--bind", "unix:/run/guest-services/backend.sock", "wsgi:app"]
28 changes: 28 additions & 0 deletions docker/dd-extension/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
IMAGE?=platerecognizer/installer
TAG?=latest

BUILDER=buildx-multi-arch

INFO_COLOR = \033[0;36m
NO_COLOR = \033[m

build-extension: ## Build service image to be deployed as a desktop extension
docker build --tag=$(IMAGE):$(TAG) .

install-extension: build-extension ## Install the extension
docker extension install $(IMAGE):$(TAG)

update-extension: build-extension ## Update the extension
docker extension update $(IMAGE):$(TAG)

prepare-buildx: ## Create buildx builder for multi-arch build, if not exists
docker buildx inspect $(BUILDER) || docker buildx create --name=$(BUILDER) --driver=docker-container --driver-opt=network=host

push-extension: prepare-buildx ## Build & Upload extension image to hub. Do not push if tag already exists: make push-extension tag=0.1
docker pull $(IMAGE):$(TAG) && echo "Failure: Tag already exists" || docker buildx build --push --builder=$(BUILDER) --platform=linux/amd64,linux/arm64 --build-arg TAG=$(TAG) --tag=$(IMAGE):$(TAG) .

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The push-extension target uses a combination of docker pull and echo to check if a tag already exists. This approach can be improved for clarity and reliability. Consider using a more explicit check with docker manifest inspect, which is specifically designed for this purpose.

-	docker pull $(IMAGE):$(TAG) && echo "Failure: Tag already exists" || docker buildx build --push --builder=$(BUILDER) --platform=linux/amd64,linux/arm64 --build-arg TAG=$(TAG) --tag=$(IMAGE):$(TAG) .
+	docker manifest inspect $(IMAGE):$(TAG) > /dev/null && echo "Failure: Tag already exists" || docker buildx build --push --builder=$(BUILDER) --platform=linux/amd64,linux/arm64 --build-arg TAG=$(TAG) --tag=$(IMAGE):$(TAG) .


help: ## Show this help
@echo Please specify a build target. The choices are:
@grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "$(INFO_COLOR)%-30s$(NO_COLOR) %s\n", $$1, $$2}'

.PHONY: help
92 changes: 92 additions & 0 deletions docker/dd-extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Plate Recognizer Installer

Plate Recognizer Installer Docker extension.

This extension is composed of:

- A [frontend](./ui) app in React.
- A [backend](./backend) container that runs an API in Python. Makes request to verify token

## Local development

You can use `docker` to build, install and push your extension. Also, we provide an opinionated [Makefile](Makefile) that could be convenient for you. There isn't a strong preference of using one over the other, so just use the one you're most comfortable with.

To build the extension, use `make build-extension` **or**:

```shell
docker buildx build -t platerecognizer/installer:latest . --load
```

To install the extension, use `make install-extension` **or**:

```shell
docker extension install platerecognizer/installer:latest
```

To Publish
```shell
docker buildx build --push --platform=linux/amd64,linux/arm64 --tag=platerecognizer/installer:0.0.1 .
```

> If you want to automate this command, use the `-f` or `--force` flag to accept the warning message.

To preview the extension in Docker Desktop, open Docker Dashboard once the installation is complete. The left-hand menu displays a new tab with the name of your extension. You can also use `docker extension ls` to see that the extension has been installed successfully.

### Frontend development

During the development of the frontend part, it's helpful to use hot reloading to test your changes without rebuilding your entire extension. To do this, you can configure Docker Desktop to load your UI from a development server.
Assuming your app runs on the default port, start your UI app and then run:

```shell
cd ui
npm install
npm run dev
```

This starts a development server that listens on port `3000`.

You can now tell Docker Desktop to use this as the frontend source. In another terminal run:

```shell
docker extension dev ui-source platerecognizer/installer:latest http://localhost:3000
```

In order to open the Chrome Dev Tools for your extension when you click on the extension tab, run:

```shell
docker extension dev debug platerecognizer/installer:latest
```

Each subsequent click on the extension tab will also open Chrome Dev Tools. To stop this behaviour, run:

```shell
docker extension dev reset platerecognizer/installer:latest
```

### Backend development

Whenever you make changes in the [backend](./backend) source code, you will need to compile them and re-deploy a new version of your backend container.
Use the `docker extension update` command to remove and re-install the extension automatically:

```shell
docker extension update platerecognizer/installer:latest
```

> If you want to automate this command, use the `-f` or `--force` flag to accept the warning message.

> Extension containers are hidden from the Docker Dashboard by default. You can change this in Settings > Extensions > Show Docker Extensions system containers.

### Clean up

To remove the extension:

```shell
docker extension rm platerecognizer/installer:latest
```

## What's next?

- To learn more about how to build your extension refer to the Extension SDK docs at <https://docs.docker.com/desktop/extensions-sdk/>.
- To publish your extension in the Marketplace visit <https://www.docker.com/products/extensions/submissions/>.
- To report issues and feedback visit <https://github.com/docker/extensions-sdk/issues>.
- To look for other ideas of new extensions, or propose new ideas of extensions you would like to see, visit <https://github.com/docker/extension-ideas/discussions>.
35 changes: 35 additions & 0 deletions docker/dd-extension/backend/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import logging
import os
import sys

from flask import Flask, request
from utils import verify_token

LOG_LEVEL = os.environ.get("LOGGING", "DEBUG").upper()

logging.basicConfig(
stream=sys.stdout,
level=LOG_LEVEL,
style="{",
format="{asctime} {levelname} {name} {threadName} : {message}",
)

lgr = logging.getLogger(__name__)


app = Flask(__name__)


@app.route("/verify-token", methods=["POST"])
def verify_token_license():
lgr.debug(f"verify token data: {request.data}")
token = request.json.get("token")
license = request.json.get("license")
valid, message = verify_token(token, license, "port" not in request.json)
lgr.info(f"verify result: {valid} - {message}")

return {"valid": valid, "message": message}
danleyb2 marked this conversation as resolved.
Show resolved Hide resolved


if __name__ == "__main__":
app.run()
2 changes: 2 additions & 0 deletions docker/dd-extension/backend/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Flask==3.0.3
gunicorn==23.0.0
danleyb2 marked this conversation as resolved.
Show resolved Hide resolved
40 changes: 40 additions & 0 deletions docker/dd-extension/backend/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
try:
from urllib.error import URLError
from urllib.request import Request, urlopen
except ImportError:
from urllib2 import Request, URLError, urlopen # type: ignore
from ssl import SSLError


def verify_token(token, license_key, is_stream=True):
if not token or not license_key:
raise ValueError("API token and license key is required.")
Comment on lines +12 to +14

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

The error message in the ValueError should be more consistent in terms of plurality. Consider changing "is" to "are" for grammatical correctness.

-        raise ValueError("API token and license key is required.")
+        raise ValueError("API token and license key are required.")


path = "stream/license" if is_stream else "sdk-webhooks"
try:
req = Request(
f"https://api.platerecognizer.com/v1/{path}/{license_key.strip()}/"
)
req.add_header("Authorization", f"Token {token.strip()}")
urlopen(req).read()
return True, None

except SSLError:
req = Request(
f"http://api.platerecognizer.com/v1/{path}/{license_key.strip()}/"
)
req.add_header("Authorization", f"Token {token.strip()}")
urlopen(req).read()
return True, None

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description CodeRabbit

Handling SSL errors by switching to HTTP is insecure. Instead, consider logging the error or notifying the user without downgrading security protocols. Here's a safer approach:

-    except SSLError:
-        req = Request(
-            f"http://api.platerecognizer.com/v1/{path}/{license_key.strip()}/"
-        )
-        req.add_header("Authorization", f"Token {token.strip()}")
-        urlopen(req).read()
-        return True, None
+    except SSLError as e:
+        return False, f"SSL Error occurred: {e}. Please check your network settings."


except URLError as e:
if "404" in str(e):
return (
False,
"The License Key cannot be found. Please use the correct License Key.",
)
elif str(403) in str(e):
return False, "The API Token cannot be found. Please use the correct Token."

else:
return True, None
danleyb2 marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 4 additions & 0 deletions docker/dd-extension/backend/wsgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from main import app

if __name__ == "__main__":
app.run()
3 changes: 3 additions & 0 deletions docker/dd-extension/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
services:
platerecognizer-installer:
image: ${DESKTOP_PLUGIN_IMAGE}
13 changes: 13 additions & 0 deletions docker/dd-extension/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions docker/dd-extension/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"icon": "logo.svg",
"vm": {
"composefile": "docker-compose.yaml",
"exposes": {
"socket": "backend.sock"
}
},
"ui": {
"dashboard-tab": {
"title": "Plate Recognizer Installer",
"src": "index.html",
"root": "ui"
}
}
}
1 change: 1 addition & 0 deletions docker/dd-extension/ui/.browserslistrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Electron 17.1.1
18 changes: 18 additions & 0 deletions docker/dd-extension/ui/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginReact from "eslint-plugin-react";

export default [
{
files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"],
},
{
languageOptions: {
globals: globals.browser,
},
},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
pluginReact.configs.flat.recommended,
];
Pefington marked this conversation as resolved.
Show resolved Hide resolved
12 changes: 12 additions & 0 deletions docker/dd-extension/ui/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="color-scheme" content="light dark" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading
Loading