Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
71 changes: 71 additions & 0 deletions docs/examples/sdk/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
Copyright 2020 Docker Compose CLI authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// This file is the first fenced Go block of docs/sdk.md, verbatim from the
// package clause down: sdk_md_test.go fails if the two ever drift, and the
// compiler keeps the documented API real.

package main

import (
"context"
"log"

"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/flags"

"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)

func main() {
ctx := context.Background()

dockerCLI, err := command.NewDockerCli()
if err != nil {
log.Fatalf("Failed to create docker CLI: %v", err)
}
err = dockerCLI.Initialize(&flags.ClientOptions{})
if err != nil {
log.Fatalf("Failed to initialize docker CLI: %v", err)
}

// Create a new Compose service instance
service, err := compose.NewComposeService(dockerCLI)
if err != nil {
log.Fatalf("Failed to create compose service: %v", err)
}

// Load the Compose project from a compose file
project, err := service.LoadProject(ctx, api.ProjectLoadOptions{
ConfigPaths: []string{"compose.yaml"},
ProjectName: "my-app",
})
if err != nil {
log.Fatalf("Failed to load project: %v", err)
}

// Start the services defined in the Compose file
err = service.Up(ctx, project, api.UpOptions{
Create: api.CreateOptions{},
Start: api.StartOptions{},
})
if err != nil {
log.Fatalf("Failed to start services: %v", err)
}

log.Printf("Successfully started project: %s", project.Name)
}
44 changes: 44 additions & 0 deletions docs/examples/sdk/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
Copyright 2020 Docker Compose CLI authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"bytes"
"os"

"github.com/docker/cli/cli/command"

"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)

// customService demonstrates the options accepted by NewComposeService: the
// second fenced Go block of docs/sdk.md is this function's body, verbatim
// (pinned by sdk_md_test.go).
func customService(dockerCLI command.Cli) (api.Compose, error) {
// Create a custom output buffer to capture logs
var outputBuffer bytes.Buffer

// Create a compose service with custom options
service, err := compose.NewComposeService(dockerCLI,
compose.WithOutputStream(&outputBuffer), // Redirect output to custom writer
compose.WithErrorStream(os.Stderr), // Use stderr for errors
compose.WithMaxConcurrency(4), // Limit concurrent operations
compose.WithPrompt(compose.AlwaysOkPrompt()), // Auto-confirm all prompts
)
return service, err
}
57 changes: 57 additions & 0 deletions docs/examples/sdk/sdk_md_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
Copyright 2020 Docker Compose CLI authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"os"
"regexp"
"strings"
"testing"

"gotest.tools/v3/assert"
)

// the compiler keeps the example alive; the doc keeps it honest
var _ = customService

var fencedGoBlocks = regexp.MustCompile("(?s)```go\n(.*?)```")

// The fenced Go blocks in docs/sdk.md are real, compiled code: the first is
// main.go from the package clause down, the second is customService's body
// in options.go. The compiler catches API drift; this test catches wording
// drift between the documentation and the compiled examples.
func TestSDKDocExamplesMatchCompiledCode(t *testing.T) {
md, err := os.ReadFile("../../sdk.md")
assert.NilError(t, err)

blocks := fencedGoBlocks.FindAllStringSubmatch(string(md), -1)
assert.Equal(t, len(blocks), 2, "docs/sdk.md is expected to hold exactly two fenced Go blocks")

mainGo, err := os.ReadFile("main.go")
assert.NilError(t, err)
// the example file carries a license header and this explanatory comment
// above the package clause; the doc block starts at the package clause
_, code, found := strings.Cut(string(mainGo), "package main")
assert.Assert(t, found)
assert.Equal(t, blocks[0][1], "package main"+code,
"the first Go block of docs/sdk.md must match docs/examples/sdk/main.go from its package clause down")

optionsGo, err := os.ReadFile("options.go")
assert.NilError(t, err)
assert.Assert(t, strings.Contains(string(optionsGo), strings.TrimRight(blocks[1][1], "\n")),
"the second Go block of docs/sdk.md must appear verbatim in docs/examples/sdk/options.go")
}
123 changes: 64 additions & 59 deletions docs/sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,52 +25,53 @@ Here's a basic example demonstrating how to load a Compose project and start the
package main

import (
"context"
"log"
"context"
"log"

"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/flags"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/flags"

"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)

func main() {
ctx := context.Background()

dockerCLI, err := command.NewDockerCli()
if err != nil {
log.Fatalf("Failed to create docker CLI: %v", err)
}
err = dockerCLI.Initialize(&flags.ClientOptions{})
if err != nil {
log.Fatalf("Failed to initialize docker CLI: %v", err)
}

// Create a new Compose service instance
service, err := compose.NewComposeService(dockerCLI)
if err != nil {
log.Fatalf("Failed to create compose service: %v", err)
}

// Load the Compose project from a compose file
project, err := service.LoadProject(ctx, api.ProjectLoadOptions{
ConfigPaths: []string{"compose.yaml"},
ProjectName: "my-app",
})
if err != nil {
log.Fatalf("Failed to load project: %v", err)
}

// Start the services defined in the Compose file
err = service.Up(ctx, project, api.UpOptions{
Create: api.CreateOptions{},
Start: api.StartOptions{},
})
if err != nil {
log.Fatalf("Failed to start services: %v", err)
}

log.Printf("Successfully started project: %s", project.Name)
ctx := context.Background()

dockerCLI, err := command.NewDockerCli()
if err != nil {
log.Fatalf("Failed to create docker CLI: %v", err)
}
err = dockerCLI.Initialize(&flags.ClientOptions{})
if err != nil {
log.Fatalf("Failed to initialize docker CLI: %v", err)
}

// Create a new Compose service instance
service, err := compose.NewComposeService(dockerCLI)
if err != nil {
log.Fatalf("Failed to create compose service: %v", err)
}

// Load the Compose project from a compose file
project, err := service.LoadProject(ctx, api.ProjectLoadOptions{
ConfigPaths: []string{"compose.yaml"},
ProjectName: "my-app",
})
if err != nil {
log.Fatalf("Failed to load project: %v", err)
}

// Start the services defined in the Compose file
err = service.Up(ctx, project, api.UpOptions{
Create: api.CreateOptions{},
Start: api.StartOptions{},
})
if err != nil {
log.Fatalf("Failed to start services: %v", err)
}

log.Printf("Successfully started project: %s", project.Name)
}
```

Expand All @@ -84,16 +85,16 @@ The `NewComposeService()` function accepts optional `compose.Option` parameters
options allow you to configure I/O streams, concurrency limits, dry-run mode, and other advanced features.

```go
// Create a custom output buffer to capture logs
var outputBuffer bytes.Buffer

// Create a compose service with custom options
service, err := compose.NewComposeService(dockerCLI,
compose.WithOutputStream(&outputBuffer), // Redirect output to custom writer
compose.WithErrorStream(os.Stderr), // Use stderr for errors
compose.WithMaxConcurrency(4), // Limit concurrent operations
compose.WithPrompt(compose.AlwaysOkPrompt()), // Auto-confirm all prompts
)
// Create a custom output buffer to capture logs
var outputBuffer bytes.Buffer

// Create a compose service with custom options
service, err := compose.NewComposeService(dockerCLI,
compose.WithOutputStream(&outputBuffer), // Redirect output to custom writer
compose.WithErrorStream(os.Stderr), // Use stderr for errors
compose.WithMaxConcurrency(4), // Limit concurrent operations
compose.WithPrompt(compose.AlwaysOkPrompt()), // Auto-confirm all prompts
)
```

### Available options
Expand All @@ -107,7 +108,7 @@ options allow you to configure I/O streams, concurrency limits, dry-run mode, an
- `WithDryRun` - Run operations in dry-run mode without actually applying changes
- `WithContextInfo(api.ContextInfo)` - Set custom Docker context information
- `WithProxyConfig(map[string]string)` - Configure HTTP proxy settings for builds
- `WithEventProcessor(progress.EventProcessor)` - Receive progress events and operation notifications
- `WithEventProcessor(api.EventProcessor)` - Receive progress events and operation notifications

These options provide fine-grained control over the SDK's behavior, making it suitable for various integration
scenarios including CLI tools, web services, automation scripts, and testing environments.
Expand Down Expand Up @@ -145,13 +146,17 @@ Common status text values include: `Creating`, `Created`, `Starting`, `Started`,

### Built-in `EventProcessor` implementations

The SDK provides three ready-to-use `EventProcessor` implementations:
The `EventProcessor` interface is defined in `github.com/docker/compose/v5/pkg/api`. When no
`WithEventProcessor` option is passed, events are silently discarded.

The renderers used by the Docker Compose CLI live in the `github.com/docker/compose/v5/cmd/display`
package and can be reused:

- `progress.NewTTYWriter(io.Writer)` - Renders an interactive terminal UI with progress bars and task lists
(similar to the Docker Compose CLI output)
- `progress.NewPlainWriter(io.Writer)` - Outputs simple text-based progress messages suitable for non-interactive
- `display.Full(out, info io.Writer, detached bool)` - Renders the interactive terminal UI with progress bars
and task lists (the default Docker Compose CLI output)
- `display.Plain(out io.Writer)` - Outputs simple text-based progress messages suitable for non-interactive
environments or log files
- `progress.NewJSONWriter()` - Render events as JSON objects
- `progress.NewQuietWriter()` - (Default) Silently processes events without producing any output
- `display.JSON(out io.Writer)` - Renders each event as a JSON object
- `display.Quiet()` - Silently discards events (same behavior as the default)

Using `EventProcessor`, a custom UI can be plugged into `docker/compose`.
Loading