document extensibility using service.provider

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2025-04-25 09:32:57 +02:00 committed by Nicolas De loof
parent fc8c56b407
commit cee6a3c660
2 changed files with 109 additions and 2 deletions

104
docs/extension.md Normal file
View File

@ -0,0 +1,104 @@
# About
The Compose application model defines `service` as an abstraction for a computing unit managing (a subset of)
application needs, which can interact with other service by relying on network(s). Docker Compose is designed
to use the Docker Engine ("Moby") API to manage services as containers, but the abstraction _could_ also cover
many other runtimes, typically cloud services or services natively provided by host.
The Compose extensibility model has been designed to extend the `service` support to runtimes accessible through
third-party tooling.
# Architecture
Compose extensibility relies on the `provider` attribute to select the actual binary responsible for managing
the resource(s) needed to run a service.
```yaml
database:
provider:
type: awesomecloud
options:
type: mysql
size: 256
```
`provider.type` tells Compose the binary to run, which can be either:
- Another Docker CLI plugin (typically, `model` to run `docker-model`)
- An executable in user's `PATH`
To be a valid Compose extension, provider command *MUST* accept subcommand `compose` (which can be hidden)
with subcommands `up` and `down`.
## Up lifecycle
To execute an application's `up` lifecycle, Compose executes the provider's `compose up` command, passing
the project name, service name and additional options. The `provider.options` are translated
into command line flags. For example:
```console
awesomecloud compose --project-name <NAME> up --type=mysql --size=256 "database"
```
> __Note:__ `project-name` _should_ be used by the provider to tag resources
> set for project, so that later execution with `down` subcommand releases
> all allocated resources set for the project.
## Communication with Compose
Providers can interact with Compose using `stdout` as a channel, sending JSON line delimited messages.
JSON messages MUST include a `type` and a `message` attribute.
```json
{ "type": "info", "message": "preparing mysql ..." }
```
`type` can be either:
- `info`: Reports status updates to the user. Compose will render message as the service state in the progress UI
- `error`: Lest the user know something went wrong with details about the error. Compose will render the message as the reason for the service failure.
- `setenv`: Let's the plugin tell Compose how dependent services can access the created resource. See next section for further details.
```mermaid
sequenceDiagram
Shell->>Compose: docker compose up
Compose->>Provider: compose up --project-name=xx --foo=bar "database"
Provider--)Compose: json { "info": "pulling 25%" }
Compose-)Shell: pulling 25%
Provider--)Compose: json { "info": "pulling 50%" }
Compose-)Shell: pulling 50%
Provider--)Compose: json { "info": "pulling 75%" }
Compose-)Shell: pulling 75%
Provider--)Compose: json { "setenv": "URL=http://cloud.com/abcd:1234" }
Compose-)Compose: set DATABASE_URL
Provider-)Compose: EOF (command complete) exit 0
Compose-)Shell: service started
```
## Connection to a service managed by a provider
A service in the Compose application can declare dependency on a service managed by an external provider:
```yaml
services:
app:
image: myapp
depends_on:
- database
database:
provider:
type: awesomecloud
```
When the provider command sends a `setenv` JSON message, Compose injects the specified variable into any dependent service,
automatically prefixing it with the service name. For example, if `awesomecloud compose up` returns:
```json
{"type": "setenv", "message": "URL=https://awesomecloud.com/db:1234"}
```
Then the `app` service, which depends on the service managed by the provider, will receive a `DATABASE_URL` environment variable injected
into its runtime environment.
> __Note:__ The `compose up` provider command _MUST_ be idempotent. If resource is already running, the command _MUST_ set
> the same environment variables to ensure consistent configuration of dependent services.
## Down lifecycle
`down` lifecycle is equivalent to `up` with the `<provider> compose --project-name <NAME> down <SERVICE>` command.
The provider is responsible for releasing all resources associated with the service.

View File

@ -59,7 +59,7 @@ func (s *composeService) runPlugin(ctx context.Context, project *types.Project,
return err
}
cmd := s.setupPluginCommand(ctx, project, provider, plugin.Path, command)
cmd := s.setupPluginCommand(ctx, project, service, plugin.Path, command)
variables, err := s.executePlugin(ctx, cmd, command, service)
if err != nil {
@ -153,11 +153,14 @@ func (s *composeService) getPluginBinaryPath(providerType string) (*manager.Plug
return manager.GetPlugin(providerType, s.dockerCli, &cobra.Command{})
}
func (s *composeService) setupPluginCommand(ctx context.Context, project *types.Project, provider types.ServiceProviderConfig, path, command string) *exec.Cmd {
func (s *composeService) setupPluginCommand(ctx context.Context, project *types.Project, service types.ServiceConfig, path, command string) *exec.Cmd {
provider := *service.Provider
args := []string{"compose", "--project-name", project.Name, command}
for k, v := range provider.Options {
args = append(args, fmt.Sprintf("--%s=%s", k, v))
}
args = append(args, service.Name)
cmd := exec.CommandContext(ctx, path, args...)
// Remove DOCKER_CLI_PLUGIN... variable so plugin can detect it run standalone