Skip to content

Commit ab91242

Browse files
authored
Merge pull request #6 from docker/cm/ddui
Add DDUI Extension
2 parents 5a7ca46 + 0bf6c6e commit ab91242

29 files changed

+9909
-0
lines changed

src/extension/.dockerignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ui/node_modules
2+
dist/install/_internal
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Build and Push Docker Image
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v4
15+
16+
- name: Set up QEMU
17+
uses: docker/setup-qemu-action@v3
18+
19+
- name: Set up Docker Buildx
20+
uses: docker/setup-buildx-action@v3
21+
22+
- name: Login to Docker Hub
23+
uses: docker/login-action@v3
24+
with:
25+
username: dockerbuildbot
26+
password: ${{ secrets.DOCKERBUILDBOT_WRITE_PAT }}
27+
28+
- name: Build and push
29+
uses: docker/build-push-action@v5
30+
with:
31+
context: .
32+
platforms: linux/amd64,linux/arm64
33+
push: true
34+
tags: |
35+
docker/labs-ai-tools-for-devs:latest
36+
docker/labs-ai-tools-for-devs:0.0.4

src/extension/.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
ui/build
3+
*.vsix
4+
binary/build

src/extension/Dockerfile

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
FROM golang:1.21-alpine AS builder
2+
ENV CGO_ENABLED=0
3+
WORKDIR /backend
4+
COPY backend/go.* .
5+
RUN --mount=type=cache,target=/go/pkg/mod \
6+
--mount=type=cache,target=/root/.cache/go-build \
7+
go mod download
8+
COPY backend/. .
9+
RUN --mount=type=cache,target=/go/pkg/mod \
10+
--mount=type=cache,target=/root/.cache/go-build \
11+
go build -trimpath -ldflags="-s -w" -o bin/service
12+
13+
FROM --platform=$BUILDPLATFORM node:21.6-alpine3.18 AS client-builder
14+
WORKDIR /ui
15+
# cache packages in layer
16+
COPY ui/package.json /ui/package.json
17+
COPY ui/package-lock.json /ui/package-lock.json
18+
RUN --mount=type=cache,target=/usr/src/app/.npm \
19+
npm set cache /usr/src/app/.npm && \
20+
npm ci
21+
# install
22+
COPY ui /ui
23+
RUN npm run build
24+
25+
FROM alpine
26+
LABEL org.opencontainers.image.title="Labs AI Tools for Devs" \
27+
org.opencontainers.image.description="Agentic workflows with Dockerized tools" \
28+
org.opencontainers.image.vendor="Docker Inc" \
29+
com.docker.desktop.extension.api.version="0.3.4" \
30+
com.docker.extension.screenshots='[{"alt":"screenshot", "url":"https://foo.bar/image1.png"}]' \
31+
com.docker.desktop.extension.icon="./docker.svg" \
32+
com.docker.extension.detailed-description="A tool to quickly install/uninstall early access VSCode extensions from Docker Labs." \
33+
com.docker.extension.publisher-url="https://www.docker.com/" \
34+
com.docker.extension.additional-urls="" \
35+
com.docker.extension.categories="utility-tools" \
36+
com.docker.extension.changelog="Initial release"
37+
38+
COPY --from=builder /backend/bin/service /
39+
COPY docker-compose.yaml .
40+
COPY metadata.json .
41+
COPY docker.svg /docker.svg
42+
COPY --from=client-builder /ui/build ui
43+
CMD /service -socket /run/guest-services/backend.sock

src/extension/Makefile

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
IMAGE?=docker/labs-ai-tools-for-devs
2+
TAG?=latest
3+
4+
BUILDER=buildx-multi-arch
5+
6+
INFO_COLOR = \033[0;36m
7+
NO_COLOR = \033[m
8+
9+
build-extension: ## Build service image to be deployed as a desktop extension
10+
docker build --tag=$(IMAGE):$(TAG) .
11+
12+
install-extension: build-extension ## Install the extension
13+
docker extension install $(IMAGE):$(TAG)
14+
15+
update-extension: build-extension ## Update the extension
16+
docker extension update $(IMAGE):$(TAG)
17+
18+
prepare-buildx: ## Create buildx builder for multi-arch build, if not exists
19+
docker buildx inspect $(BUILDER) || docker buildx create --name=$(BUILDER) --driver=docker-container --driver-opt=network=host
20+
21+
push-extension: prepare-buildx ## Build & Upload extension image to hub. Do not push if tag already exists: make push-extension tag=0.1
22+
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) .
23+
24+
help: ## Show this help
25+
@echo Please specify a build target. The choices are:
26+
@grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "$(INFO_COLOR)%-30s$(NO_COLOR) %s\n", $$1, $$2}'
27+
28+
.PHONY: help

src/extension/README.md

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# DDUI Extension
2+
3+
A Docker Desktop extension to run prompts.
4+
5+
https://github.com/docker/labs-ai-tools-for-devs
6+
7+
![demo image](./image.png)
8+
9+
## Development
10+
11+
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.
12+
13+
To build the extension, use `make build-extension` **or**:
14+
15+
```shell
16+
docker buildx build -t docker/labs-ai-tools-for-devs:latest . --load
17+
```
18+
19+
To install the extension, use `make install-extension` **or**:
20+
21+
```shell
22+
docker extension install docker/labs-ai-tools-for-devs:latest
23+
```
24+
25+
> If you want to automate this command, use the `-f` or `--force` flag to accept the warning message.
26+
27+
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.
28+
29+
### Frontend development
30+
31+
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.
32+
Assuming your app runs on the default port, start your UI app and then run:
33+
34+
```shell
35+
cd ui
36+
npm install
37+
npm run dev
38+
```
39+
40+
This starts a development server that listens on port `3000`.
41+
42+
You can now tell Docker Desktop to use this as the frontend source. In another terminal run:
43+
44+
```shell
45+
docker extension dev ui-source vonwig/labs-ai-tools-for-devs:0.0.1 http://localhost:3000
46+
```
47+
48+
In order to open the Chrome Dev Tools for your extension when you click on the extension tab, run:
49+
50+
```shell
51+
docker extension dev debug docker/labs-ai-tools-for-devs:latest
52+
```
53+
54+
Each subsequent click on the extension tab will also open Chrome Dev Tools. To stop this behaviour, run:
55+
56+
```shell
57+
docker extension dev reset docker/labs-ai-tools-for-devs:latest
58+
```
59+
60+
### Backend development (optional)
61+
62+
This example defines an API in Go that is deployed as a backend container when the extension is installed. This backend could be implemented in any language, as it runs inside a container. The extension frameworks provides connectivity from the extension UI to a socket that the backend has to connect to on the server side.
63+
64+
Note that an extension doesn't necessarily need a backend container, but in this example we include one for teaching purposes.
65+
66+
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.
67+
Use the `docker extension update` command to remove and re-install the extension automatically:
68+
69+
```shell
70+
docker extension update docker/labs-ai-tools-for-devs:latest
71+
```
72+
73+
> If you want to automate this command, use the `-f` or `--force` flag to accept the warning message.
74+
75+
> Extension containers are hidden from the Docker Dashboard by default. You can change this in Settings > Extensions > Show Docker Extensions system containers.
76+
77+
### Clean up
78+
79+
To remove the extension:
80+
81+
```shell
82+
docker extension rm docker/labs-ai-tools-for-devs:latest
83+
```

src/extension/backend/go.mod

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module labs-ai-tools-for-devs
2+
3+
go 1.21.10
4+
5+
require (
6+
github.com/labstack/echo/v4 v4.12.0
7+
github.com/sirupsen/logrus v1.9.3
8+
)
9+
10+
require (
11+
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
12+
github.com/labstack/gommon v0.4.2 // indirect
13+
github.com/mattn/go-colorable v0.1.13 // indirect
14+
github.com/mattn/go-isatty v0.0.20 // indirect
15+
github.com/valyala/bytebufferpool v1.0.0 // indirect
16+
github.com/valyala/fasttemplate v1.2.2 // indirect
17+
golang.org/x/crypto v0.22.0 // indirect
18+
golang.org/x/net v0.24.0 // indirect
19+
golang.org/x/sys v0.19.0 // indirect
20+
golang.org/x/text v0.14.0 // indirect
21+
golang.org/x/time v0.5.0 // indirect
22+
)

src/extension/backend/go.sum

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
5+
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
6+
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
7+
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
8+
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
9+
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
10+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
11+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
12+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
13+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
14+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
15+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
16+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
17+
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
18+
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
19+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
20+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
21+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
22+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
23+
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
24+
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
25+
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
26+
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
27+
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
28+
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
29+
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
30+
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
31+
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
32+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
33+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
34+
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
35+
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
36+
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
37+
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
38+
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
39+
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
40+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
41+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
42+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
43+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

src/extension/backend/main.go

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"net"
6+
"os"
7+
8+
"github.com/labstack/echo/v4"
9+
"github.com/labstack/echo/v4/middleware"
10+
"github.com/sirupsen/logrus"
11+
)
12+
13+
var logger = logrus.New()
14+
15+
func main() {
16+
var socketPath string
17+
flag.StringVar(&socketPath, "socket", "/run/guest-services/backend.sock", "Unix domain socket to listen on")
18+
flag.Parse()
19+
20+
_ = os.RemoveAll(socketPath)
21+
22+
logger.SetOutput(os.Stdout)
23+
24+
logMiddleware := middleware.LoggerWithConfig(middleware.LoggerConfig{
25+
Skipper: middleware.DefaultSkipper,
26+
Format: `{"time":"${time_rfc3339_nano}","id":"${id}",` +
27+
`"method":"${method}","uri":"${uri}",` +
28+
`"status":${status},"error":"${error}"` +
29+
`}` + "\n",
30+
CustomTimeFormat: "2006-01-02 15:04:05.00000",
31+
Output: logger.Writer(),
32+
})
33+
34+
logger.Infof("Starting listening on %s\n", socketPath)
35+
router := echo.New()
36+
router.HideBanner = true
37+
router.Use(logMiddleware)
38+
startURL := ""
39+
40+
ln, err := listen(socketPath)
41+
if err != nil {
42+
logger.Fatal(err)
43+
}
44+
router.Listener = ln
45+
46+
logger.Fatal(router.Start(startURL))
47+
}
48+
49+
func listen(path string) (net.Listener, error) {
50+
return net.Listen("unix", path)
51+
}
52+
53+
type HTTPMessageBody struct {
54+
Message string
55+
}

src/extension/docker-compose.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
services:
2+
labs-ai-tools-for-devs:
3+
image: ${DESKTOP_PLUGIN_IMAGE}
4+
volumes:
5+
openai_key:
6+
driver: local
7+

0 commit comments

Comments
 (0)