diff --git a/docs/examples/sdk/main.go b/docs/examples/sdk/main.go new file mode 100644 index 00000000000..6787bcf2a14 --- /dev/null +++ b/docs/examples/sdk/main.go @@ -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) +} diff --git a/docs/examples/sdk/options.go b/docs/examples/sdk/options.go new file mode 100644 index 00000000000..a132a74aa3c --- /dev/null +++ b/docs/examples/sdk/options.go @@ -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 +} diff --git a/docs/examples/sdk/sdk_md_test.go b/docs/examples/sdk/sdk_md_test.go new file mode 100644 index 00000000000..27d2d51703f --- /dev/null +++ b/docs/examples/sdk/sdk_md_test.go @@ -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") +} diff --git a/docs/sdk.md b/docs/sdk.md index 3a03c7fdc1d..c0c0c39f3ea 100644 --- a/docs/sdk.md +++ b/docs/sdk.md @@ -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) } ``` @@ -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 @@ -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. @@ -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`.