Skip to content

Commit 0995801

Browse files
authored
Merge pull request #60 from gtardif/api_docker_cli_sample
Extension sample and tutorial about UI invoking docker commands
2 parents 6c6abec + e521dca commit 0995801

File tree

9 files changed

+293
-4
lines changed

9 files changed

+293
-4
lines changed

docs/dev/api/overview.md

+28-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,31 @@ Displaying an error in a red banner on the Dashboard:
2222
window.ddClient.toastError("Something went wrong");
2323
```
2424

25+
## Running any docker command and getting results
26+
27+
```typescript
28+
window.ddClient.backend
29+
.execDockerCmd("info", "--format", '"{{ json . }}"')
30+
.then((cmdResult) => console.log(cmdResult));
31+
```
32+
33+
result will be of the form:
34+
35+
```json
36+
{
37+
"stderr": "",
38+
"stdout": "{...}"
39+
}
40+
```
41+
42+
(In this example the docker command output is a json output)
43+
44+
For convenience, the command result object also has methods to easily parse it:
45+
46+
* `cmdResult.lines() : string[]` split output lines
47+
* `cmdResult.parseJsonObject() : any` parse a well formed json output
48+
* `cmdResult.parseJsonLines() : any[]` parse each output line as a json object
49+
2550
## Communication with the Extension Backend
2651

2752
Accessing a socket exposed by your extension VM service:
@@ -37,13 +62,13 @@ Running a command in the container inside the VM:
3762
```typescript
3863
window.ddClient.backend
3964
.execInVMExtension(`cliShippedInTheVm xxx`)
40-
.then((value: any) => console.log(value));
65+
.then((cmdResult: any) => console.log(cmdResult));
4166
```
4267

4368
Invoking an extension binary on your host:
4469

4570
```typescript
46-
window.ddClient.execHostCmd(`cliShippedOnHost xxx`).then((value: any) => {
47-
console.log(value);
71+
window.ddClient.execHostCmd(`cliShippedOnHost xxx`).then((cmdResult: any) => {
72+
console.log(cmdResult);
4873
});
4974
```
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
This tutorial describes a minimal example running frontend extension and invoking docker CLI commands.
2+
3+
## Prerequisites
4+
5+
- [Docker Desktop build with Extensions capabilities](https://github.com/docker/desktop-extension-samples/releases/)
6+
- [Docker Extensions CLI](https://github.com/docker/desktop-extension-samples/releases/)
7+
8+
## Extension folder structure
9+
10+
A Desktop Extension is comprised of several files, ranging from the extension's source code to required Extension-specific files.
11+
12+
In the `minimal-docker-cli` folder, at the root of the repository, you can find a ready-to-go example that represents a UI Extension invoking docker commands. We will go through this code example in this tutorial.
13+
14+
```bash
15+
.
16+
├── Dockerfile # (1)
17+
├── metadata.json # (2)
18+
└── ui # (3)
19+
├── index.html
20+
└── script.js
21+
```
22+
23+
1. Contains everything required to build the extension and run it in Docker Desktop.
24+
2. A file that provides information about the extension such as the name, description, and version, among others.
25+
3. The source folder that contains all your HTML, CSS and JS files. These can also be other static assets like logos, icons, etc.
26+
27+
## The extension's Dockerfile
28+
29+
An extension requires a `Dockerfile` to build, publish and run in Docker Desktop.
30+
31+
The bare minimum configuration that a Dockerfile's extension requires to function properly is:
32+
33+
- Labels - required to provide extra information about the extension.
34+
- The source code - in this case, an `index.html` that sits within the `ui` folder. `index.html` refers to javascript code in `script.js`.
35+
- The `metadata.json` file.
36+
37+
```Dockerfile title="Dockerfile" linenums="1"
38+
FROM scratch
39+
40+
LABEL org.opencontainers.image.title="MyExtension" \
41+
org.opencontainers.image.description="A sample extension to show how easy it's to get started with Desktop Extensions." \
42+
org.opencontainers.image.vendor="Docker Inc." \
43+
com.docker.desktop.extension.api.version="1.0.0-beta.1"
44+
45+
COPY ui ./ui
46+
COPY metadata.json .
47+
```
48+
49+
## Configure the Extension metadata file
50+
51+
A `metadata.json` file is required at the root of the image filesystem.
52+
53+
```json title="metadata.json" linenums="1"
54+
{
55+
"desktop-plugin-version": "1.0.0-beta.1",
56+
"name": "MyExtension",
57+
"provider": "Docker Inc.",
58+
"ui": {
59+
"dashboard-tab": {
60+
"title": "My Extension",
61+
"root": "/ui",
62+
"src": "index.html"
63+
}
64+
}
65+
}
66+
```
67+
68+
## Invoke docker CLI in your javascript code
69+
70+
A `script.js` includes code that is executed when the extension tab is shown.
71+
72+
In Javascript, extensions can use `windows.ddClient` to get access to the Docker Desktop extension API.
73+
74+
On this ddClient object we can invoke `ddClient.ExecDockerCmd("sytem", "df", "--format", "'{{ json . }}'")`, and then use `res.parseJsonLines()` to read results as json objects.
75+
76+
The rest is purely formatting code using the output of the Docker command:
77+
78+
```javascript
79+
window.ddClient
80+
.execDockerCmd("system", "df", "--format", "'{{ json . }}'")
81+
.then((res) => {
82+
document.getElementById("size-info").innerHTML = `
83+
<table>
84+
<tr> <th>Type</th> <th>Active</th> <th>Total</th> <th>Size</th> <th>Reclaimable</th> </tr>
85+
${res
86+
.parseJsonLines()
87+
.map(
88+
(cat) =>
89+
`<tr> <td>${cat.Type}</td> <td>${cat.Active}</td> <td>${cat.TotalCount}</td> <td>${cat.Size}</td> <td>${cat.Reclaimable}</td> </tr>`
90+
)
91+
.join("")}
92+
</table>
93+
`;
94+
});
95+
```
96+
97+
## Build the extension
98+
99+
```bash
100+
docker build -t desktop-docker-cli-minimal-extension:0.0.1 .
101+
```
102+
103+
### Build the extension for multiple platforms
104+
105+
```bash
106+
docker buildx build --platform=linux/amd64,linux/arm64 -t desktop-docker-cli-minimal-extension:0.0.1 .
107+
```
108+
109+
## Validate the extension
110+
111+
Next, verify the extension image complies with the requisites to be a compliant Desktop Extension.
112+
113+
The validation will check if the extension's `Dockerfile` specifies all the required labels and if the metadata file is valid against the JSON schema file.
114+
115+
```bash
116+
docker extension validate desktop-docker-cli-minimal-extension:0.0.1
117+
```
118+
119+
If your extension is valid, you should see the following message:
120+
121+
`The extension image "desktop-docker-cli-minimal-extension:0.0.1" is valid`.
122+
123+
## Install the extension
124+
125+
Now that the extension is packaged as a Docker image, let's proceed with the installation. To do so, we'll use the Docker Extensions CLI.
126+
127+
!!! info "Enable Docker Desktop Extensions"
128+
129+
Ensure the Extensions capabilities are enabled in the Docker Desktop build by running `docker extension enable`
130+
131+
To install the extension in Docker Desktop, run:
132+
133+
```bash
134+
docker extension install desktop-docker-cli-minimal-extension:0.0.1
135+
```
136+
137+
If the installation was successful, you should see the following output:
138+
139+
```bash
140+
Installing new extension "MyExtension" with desktop-docker-cli-minimal-extension:0.0.1 ...
141+
Installing Desktop extension UI for tab "My Extension"...
142+
Extension UI tab "Disk usage" added.
143+
Extension "MyExtension" installed successfully
144+
```
145+
146+
## Preview the extension
147+
148+
You can verify that the extension has been installed successfully using the following CLI command:
149+
150+
```bash
151+
docker extension ls
152+
```
153+
154+
It outputs all the extensions installed:
155+
156+
```bash
157+
PLUGIN PROVIDER IMAGE UI VM HOST
158+
MyExtension Docker Inc. desktop-docker-cli-minimal-extension:0.0.1 1 tab(My Extension) - -
159+
```
160+
161+
To preview the extension in Docker Desktop, close and open the Docker Desktop Dashboard once the installation has completed.
162+
163+
On the left menu, you should see a new tab with the name `Disk usage`. Click on it to load the main window that will run the javascript code, invoke the `docker system df` command, and render the results.
164+
165+
![UI Extension](images/docker-cli-minimal-extension.png)
166+
167+
## Publish the extension
168+
169+
In order to publish the extension, we have to upload the Docker image to [DockerHub](https://hub.docker.com).
170+
171+
Let's tag the previous image to preprend the account owner at the beginning of the image name:
172+
173+
```bash
174+
docker tag desktop-docker-cli-minimal-extension:0.0.1 owner/desktop-docker-cli-minimal-extension:0.0.1
175+
```
176+
177+
```bash
178+
docker push owner/desktop-docker-cli-minimal-extension:0.0.1
179+
```
180+
181+
!!! warning
182+
183+
Note that for Docker Extensions images to be listed in Docker Desktop, they must be approved by Docker and be tagged following semantic versioning, e.g: `0.0.1`.
184+
185+
See [distribution and new releases](../extensions/DISTRIBUTION.md#distribution-and-new-releases) for more information.
186+
187+
See <a href="https://semver.org/" target="__blank">semver.org</a> to learn more about semantic versioning.
188+
189+
!!! info "Having trouble to push the image?"
190+
191+
Ensure you are logged into DockerHub. Otherwise, run `docker login` to authenticate.
192+
193+
## Clean up
194+
195+
```bash
196+
docker extension rm desktop-docker-cli-minimal-extension
197+
```
198+
199+
The following output should be displayed:
200+
201+
```bash
202+
Removing extension MyExtension...
203+
Extension UI tab Disk usage removed
204+
Extension "MyExtension" removed
205+
```
206+
207+
## What's next?
208+
209+
See the next [tutorial](../minimal-backend-extension) to create a minimal backend extension.

minimal-backend/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
extension: ## Build service image to be deployed as a desktop extension
2-
docker build --tag=desktop-backend-minimal-extension .
2+
docker build --tag=desktop-minimal-docker-cli-extension .

minimal-docker-cli/Dockerfile

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FROM scratch
2+
3+
LABEL org.opencontainers.image.title="Docker disk usage" \
4+
org.opencontainers.image.description="A sample extension to show how to run docker commands from Desktop Extensions." \
5+
org.opencontainers.image.vendor="Docker Inc." \
6+
com.docker.desktop.extension.api.version="1.0.0-beta.1"
7+
8+
COPY ui ./ui
9+
COPY metadata.json .

minimal-docker-cli/Makefile

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
extension: ## Build service image to be deployed as a desktop extension
2+
docker build --tag=desktop-docker-cli-minimal-extension .

minimal-docker-cli/metadata.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"desktop-plugin-version": "1.0.0-beta.1",
3+
"name": "MyExtension",
4+
"provider": "Docker Inc.",
5+
"ui": {
6+
"dashboard-tab": {
7+
"title": "Disk usage",
8+
"root": "/ui",
9+
"src": "index.html"
10+
}
11+
}
12+
}

minimal-docker-cli/ui/index.html

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
7+
<body class="dockerDesktopTheme">
8+
<h1>Docker Disk usage</h1>
9+
<p>This Desktop Extension will execute a docker command ('docker system df') and display Docker disk usage.</p>
10+
11+
<div id="size-info">
12+
</div>
13+
14+
<script type="application/javascript" src="./script.js"></script>
15+
</body>
16+
</html>

minimal-docker-cli/ui/script.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
window.ddClient
2+
.execDockerCmd("system", "df", "--format", '"{{ json . }}"')
3+
.then((res) => {
4+
document.getElementById("size-info").innerHTML = `
5+
<table>
6+
<tr> <th>Type</th> <th>Active</th> <th>Total</th> <th>Size</th> <th>Reclaimable</th> </tr>
7+
${res
8+
.parseJsonLines()
9+
.map(
10+
(cat) =>
11+
`<tr> <td>${cat.Type}</td> <td>${cat.Active}</td> <td>${cat.TotalCount}</td> <td>${cat.Size}</td> <td>${cat.Reclaimable}</td> </tr>`
12+
)
13+
.join("")}
14+
</table>
15+
`;
16+
});

0 commit comments

Comments
 (0)