diff --git a/_data/toc.yaml b/_data/toc.yaml index f338e66944..f15099c420 100644 --- a/_data/toc.yaml +++ b/_data/toc.yaml @@ -891,7 +891,7 @@ reference: - path: /desktop/extensions-sdk/dev/api/reference/interfaces/ExecResultV0/ title: ExecResultV0 - path: /desktop/extensions-sdk/dev/api/reference/interfaces/BackendV0/ - title: BackendV0 + title: BackendV0 - title: Dockerfile reference path: /engine/reference/builder/ - sectiontitle: Compose file reference @@ -1179,14 +1179,14 @@ manuals: title: Quickstart - sectiontitle: "Part one: Build" section: - - sectiontitle: "Step one: Set up..." + - sectiontitle: "Step one: Create a new extension" section: - - title: ...a minimal frontend extension + - title: Create a simple extension path: /desktop/extensions-sdk/build/set-up/minimal-frontend-extension/ - - title: ... an advanced frontend extension + - title: Create a more advanced frontend path: /desktop/extensions-sdk/build/set-up/frontend-extension-tutorial/ - - title: ... a minimal backend extension - path: /desktop/extensions-sdk/build/set-up/minimal-backend-extension/ + - title: Add a backend to your extension + path: /desktop/extensions-sdk/build/set-up/backend-extension-tutorial/ - title: "Step two: Build and install" path: /desktop/extensions-sdk/build/build-install/ - title: "Step three: Test and debug" diff --git a/desktop/extensions-sdk/build/set-up/backend-extension-tutorial.md b/desktop/extensions-sdk/build/set-up/backend-extension-tutorial.md new file mode 100644 index 0000000000..0715888410 --- /dev/null +++ b/desktop/extensions-sdk/build/set-up/backend-extension-tutorial.md @@ -0,0 +1,420 @@ +--- +title: Add a backend to your extension +description: Learn how to add a backend to your extension. +keywords: Docker, extensions, sdk, build +redirect_from: + - /desktop/extensions-sdk/tutorials/minimal-backend-extension/ + - /desktop/extensions-sdk/build/minimal-backend-extension/ +--- + +Your extension can ship a backend part with which the frontend can interact with. This page provides information on +why and how to add a backend. + +> Note +> +> Before you start, make sure you have installed the latest version of [Docker Desktop](https://www.docker.com/products/docker-desktop/). + +> Note +> +> If you want to start a codebase for your new extension, our [Quickstart guide](../../quickstart.md) and `docker extension init ` provides a better base for your extension as it is more up-to-date and related to your install of Docker Desktop. + +## Why add a backend? + +Thanks to the Docker Extensions SDK, most of the time you should be able to do what you need from the Docker CLI +directly from [the frontend](./frontend-extension-tutorial.md#use-the-extension-apis-client). + +Nonetheless, there are some cases where you might need to add a backend to your extension. So far, extension +builders have used the backend to: +- Store data in a local database and serve them back with a REST API. +- Store the extension state, like when a button starts a long-running process, so that if you navigate away + from the extension user interface and come back, the frontend can pick up where it left off. + +## Add a backend to the extension + +If you created your extension using the `docker extension init` command, you already have a backend set up. If it is +not the case, then you have to first create a `vm` directory that will contain the code and update the Dockerfile to +containerize it. + +Here is the extension folder structure with a backend: + +```bash +. +├── Dockerfile # (1) +├── Makefile +├── metadata.json +├── ui + └── index.html +└── vm # (2) + ├── go.mod + └── main.go +``` + +1. Contains everything required to build the backend and copy it in the extension's container filesystem. +2. The source folder that contains the backend code of the extension + +Although you can start from an empty directory or from the `vm-ui extension` [sample](https://github.com/docker/extensions-sdk/tree/main/samples){:target="_blank" rel="noopener" class="_"}, +it is highly recommended that you start from the `docker extension init` command and change it to suit your needs. + +> **Tip** +> +> The `docker extension init` generates a Go backend. But you can still use it as a starting point for +> your own extension and use any other language like Node.js, Python, Java, .Net, or any other language and framework. +{: .tip } + +On this tutorial, the backend service simply exposes one route that returns a JSON payload that says "Hello". + +```json +{ "Message": "Hello" } +``` + +> **Important** +> +> We recommend that, the frontend and the backend communicate through sockets (and named pipes on Windows) instead of +> HTTP. On one hand, because it will prevent port collision with any other running application or container running +> on the host. On the other hand, because some Docker Desktop users are running in constrained environments where they +> can't open ports on their machines. So, when choosing the language and framework for your backend, make sure it +> supports sockets connection. +{: .important} + + + +
+
+ +```go +package main + +import ( + "flag" + "log" + "net" + "net/http" + "os" + + "github.com/labstack/echo" + "github.com/sirupsen/logrus" +) + +func main() { + var socketPath string + flag.StringVar(&socketPath, "socket", "/run/guest/volumes-service.sock", "Unix domain socket to listen on") + flag.Parse() + + os.RemoveAll(socketPath) + + logrus.New().Infof("Starting listening on %s\n", socketPath) + router := echo.New() + router.HideBanner = true + + startURL := "" + + ln, err := listen(socketPath) + if err != nil { + log.Fatal(err) + } + router.Listener = ln + + router.GET("/hello", hello) + + log.Fatal(router.Start(startURL)) +} + +func listen(path string) (net.Listener, error) { + return net.Listen("unix", path) +} + +func hello(ctx echo.Context) error { + return ctx.JSON(http.StatusOK, HTTPMessageBody{Message: "hello world"}) +} + +type HTTPMessageBody struct { + Message string +} +``` + +
+
+ +
+ +> **Important** +> +> We don't have a working example for Node yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.25798127=Node) +> and let us know you'd like a sample for Node. +{: .important } + +
+
+ +
+ +> **Important** +> +> We don't have a working example for Python yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.25798127=Python) +> and let us know you'd like a sample for Python. +{: .important } + +
+
+ +
+ +> **Important** +> +> We don't have a working example for Java yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.25798127=Java) +> and let us know you'd like a sample for Java. +{: .important } + +
+
+ +
+ +> **Important** +> +> We don't have a working example for .Net. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.25798127=.Net) +> and let us know you'd like a sample for .Net. +{: .important } + +
+
+ +## Adapt the Dockerfile + +> **Note** +> +> When using the `docker extension init`, it creates a `Dockerfile` that already contains what is needed for a Go backend. + + + +
+
+ +
+ +To deploy your Go backend when installing the extension, you need first to configure the `Dockerfile` so that: +- it builds the backend application +- it copies the binary in the extension's container filesystem +- it starts the binary when the container starts listening on the extension socket + +```dockerfile +FROM node:17.7-alpine3.14 AS client-builder +# ... build frontend application + +# Build the Go backend +FROM golang:1.17-alpine AS builder +ENV CGO_ENABLED=0 +WORKDIR /backend +COPY vm/go.* . +RUN --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + go mod download +COPY vm/. . +RUN --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + go build -trimpath -ldflags="-s -w" -o bin/service + +FROM alpine:3.15 +# ... add labels and copy the frontend application + +COPY --from=builder /backend/bin/service / +CMD /service -socket /run/guest-services/extension-allthethings-extension.sock +``` + +
+
+ +
+ +> **Important** +> +> We don't have a working Dockerfile for Node yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.25798127=Node) +> and let us know you'd like a Dockerfile for Node. +{: .important } + +
+
+ +
+ +> **Important** +> +> We don't have a working Dockerfile for Python yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.25798127=Python) +> and let us know you'd like a Dockerfile for Python. +{: .important } + +
+
+ +
+ +> **Important** +> +> We don't have a working Dockerfile for Java yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.25798127=Java) +> and let us know you'd like a Dockerfile for Java. +{: .important } + +
+
+ +
+ +> **Important** +> +> We don't have a working Dockerfile for .Net. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.25798127=.Net) +> and let us know you'd like a Dockerfile for .Net. +{: .important } + +
+
+ +## Configure the metadata file + +To start the backend service of your extension inside the VM of Docker Desktop, you have to configure the image name +in the `vm` section of the `metadata.json` file. + +```json +{ + "vm": { + "image": "${DESKTOP_PLUGIN_IMAGE}" + }, + "icon": "docker.svg", + "ui": { + ... + } +} +``` + +For more information on the `vm` section of the `metadata.json`, see [Metadata](../../extensions/METADATA.md). + +> **Warning** +> +> Do not replace the `${DESKTOP_PLUGIN_IMAGE}` placeholder in the `metadata.json` file. The placeholder is replaced automatically with the correct image name when the extension is installed. +{: .warning} + +## Invoke the extension backend from your frontend + +Using the [advanced frontend extension example](./frontend-extension-tutorial.md), we can invoke our extension backend. + +Use the Docker Desktop Client object and then invoke the `/hello` route from the backend service with `ddClient. +extension.vm.service.get` that returns the body of the response. + + + +
+
+ +Replace the `ui/src/App.tsx` file with the following code: + +```tsx +{% raw %} +// ui/src/App.tsx +import React, { useEffect } from 'react'; +import { createDockerDesktopClient } from "@docker/extension-api-client"; + +//obtain docker destkop extension client +const ddClient = createDockerDesktopClient(); + +export function App() { + const ddClient = createDockerDesktopClient(); + const [hello, setHello] = useState(); + + useEffect(() => { + const getHello = async () => { + const result = await ddClient.extension.vm?.service?.get('/hello'); + setHello(JSON.stringify(result)); + } + getHello() + }, []); + + return ( + {hello} + ); +} +{% endraw %} +``` + +
+
+ +
+ +> **Important** +> +> We don't have an example for Vue yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.1333218187=Vue) +> and let us know you'd like a sample with Vue. +{: .important } + +
+
+ +
+ +> **Important** +> +> We don't have an example for Angular yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.1333218187=Angular) +> and let us know you'd like a sample with Angular. +{: .important } + +
+
+ +
+ +> **Important** +> +> We don't have an example for Svelte yet. [Fill out the form](https://docs.google.com/forms/d/e/1FAIpQLSdxJDGFJl5oJ06rG7uqtw1rsSBZpUhv_s9HHtw80cytkh2X-Q/viewform?usp=pp_url&entry.1333218187=Svelte) +> and let us know you'd like a sample with Svelte. +{: .important } + +
+
+ +## Re-build the extension and update it + +Since you have modified the configuration of the extension and added a stage in the Dockerfile, you must build again +the extension. + +```bash +docker build --tag= awesome-inc/my-extension:latest . +``` + +Once built, you need to update it (or install it if you haven't done it yet). + +```bash +docker extension update awesome-inc/my-extension:latest +``` + +Now you can see the backend service running in the containers tab of the Docker Desktop Dashboard and watch the logs +when you need to debug it. + +> **Tip** +> +> You may need to enable the "Show system containers" option in Docker Desktop to see the backend container running +> under the extension compose project in the containers tab of the dashboard. +> See [how to show extension containers](../test-debug.md#show-the-extension-containers) for more information. +{: .tip } + +Open Docker Desktop Dashboard and click on the containers tab. You should see the response from the backend service +call displayed. + +## What's next? + +Learn how to [share and publish your extension](../../extensions/index.md). diff --git a/desktop/extensions-sdk/build/set-up/frontend-extension-tutorial.md b/desktop/extensions-sdk/build/set-up/frontend-extension-tutorial.md index 39deae2f2e..238b5f3221 100644 --- a/desktop/extensions-sdk/build/set-up/frontend-extension-tutorial.md +++ b/desktop/extensions-sdk/build/set-up/frontend-extension-tutorial.md @@ -142,7 +142,8 @@ COPY --from=client-builder /ui/build ui ## Configure the metadata file -A `metadata.json` file is required at the root of your extension directory. +In order to add a tab in Docker Desktop for your extension, you have to configure it in the `metadata.json` +file the root of your extension directory. ```json { @@ -157,9 +158,34 @@ A `metadata.json` file is required at the root of your extension directory. } ``` +The `title` property is the name of the extension that is displayed in the left-menu of the Docker Desktop Dashboard. +The `root` property is the path to the frontend application in the extension's container filesystem used by the +system to deploy it on the host. +The `src` property is the path to the HTML entry point of the frontend application within the `root` folder. + +For more information on the `ui` section of hte `metadata.json`, see [Metadata](../../extensions/METADATA.md#ui-section). + +## Build the extension and install it + +Now that you have configured the extension, you need to build the extension image that Docker Desktop will use to +install it. + +```bash +docker build --tag= awesome-inc/my-extension:latest . +``` + +This built an image tagged `awesome-inc/my-extension:latest`, you can run `docker inspect +awesome-inc/my-extension:latest` to see more details about it. + +Finally, you can install the extension and see it appearing in the Docker Desktop Dashboard. + +```bash +docker extension install awesome-inc/my-extension:latest +``` + ## Use the Extension APIs client -To use the Extension APIs and perform actions with Docker Desktop, the extension must first import the +To use the Extension APIs and perform actions with Docker Desktop, the extension must first import the `@docker/extension-api-client` library. To install it, run the command below: ```bash @@ -174,7 +200,7 @@ import { createDockerDesktopClient } from '@docker/extension-api-client'; const ddClient = createDockerDesktopClient(); ``` -When using Typescript, you can also install `@docker/extension-api-client-types` as a dev dependency. This will +When using Typescript, you can also install `@docker/extension-api-client-types` as a dev dependency. This will provide you with type definitions for the extension APIs and auto-completion in your IDE. ```bash @@ -183,7 +209,7 @@ npm install @docker/extension-api-client-types --save-dev ![types auto complete](images/types-autocomplete.png) -For example, you can use the `docker.cli.exec` function to get the list of all the containers via the `docker ps --all` +For example, you can use the `docker.cli.exec` function to get the list of all the containers via the `docker ps --all` command and display the result in a table.