Skip to content

Commit

Permalink
feat(compose): Docker Compose plugin ready
Browse files Browse the repository at this point in the history
The Docker Compose plugin is now ready for general use. We're still
labelling it as experimental.

Tests were added for the main action handlers and flows, and several
things fixed and improved along the way. Also added an initial usage
guide and improved the docstrings.
  • Loading branch information
thsig committed Jul 31, 2023
1 parent afae714 commit d8e43ca
Show file tree
Hide file tree
Showing 83 changed files with 8,708 additions and 280 deletions.
20 changes: 17 additions & 3 deletions core/src/graph/solver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,9 +353,18 @@ export class GraphSolver extends TypedEventEmitter<SolverEvents> {
this.emit("loop", {})
})
.catch((error) => {
this.garden.events.emit("internalError", { error, timestamp: new Date() })
this.logInternalError(node, error)
node.complete({ startedAt, error, aborted: true, result: null })
if (error.type === "graph") {
// These errors are usually due to references to missing or disabled dependencies, not internal errors
// per se (so we also don't want to emit "internalError" events for these).
this.logDependencyError(node, error)
} else {
this.garden.events.emit("internalError", { error, timestamp: new Date() })
this.logInternalError(node, error)
}
try {
// This may fail if the node's task's dependencies include one or more missing or disabled actions.
node.complete({ startedAt, error, aborted: true, result: null })
} catch (err) {}
// Abort execution on internal error
this.emit("abort", { error })
})
Expand Down Expand Up @@ -496,6 +505,11 @@ export class GraphSolver extends TypedEventEmitter<SolverEvents> {
this.logError(log, err, prefix)
}

private logDependencyError(node: TaskNode, err: Error) {
const prefix = `A graph or dependency error occurred while ${node.describe()}. Here is the output:`
this.logError(node.task.log, err, prefix)
}

private logInternalError(node: TaskNode, err: Error) {
const prefix = `An internal error occurred while ${node.describe()}. Here is the output:`
this.logError(node.task.log, err, prefix)
Expand Down
2 changes: 2 additions & 0 deletions core/src/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ export class ActionRouter extends BaseRouter {
handlerType: "getOutputs",
// TODO: figure out why the typing clashes here
params: { ...params, action: <any>params.action, events: undefined },
// TODO: When rolling out the plugin SDK, warn if output schema validation fails due to the default handler
// being used.
defaultHandler: async ({}) => ({ outputs: {} }),
})

Expand Down
2 changes: 1 addition & 1 deletion core/src/util/ext-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export class CliWrapper {
outputStream.on("data", (data: Buffer) => {
const msg = data.toString()

if (streamLogs.print) {
if (streamLogs.print && msg && msg.length > 0) {
logEventContext.log.info(hasAnsi(msg) ? msg : chalk.white(msg))
}

Expand Down
20 changes: 12 additions & 8 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,21 @@
* [Provider Configuration](./pulumi-plugin/configure-provider.md)
* [Deploy action Configuration](./pulumi-plugin/configure-deploy-actions.md)

## 🌼 Other Plugins
## 🌼 Docker Compose Plugin

* [About](./docker-compose-plugin/about.md)

## 🌷 Other Plugins

* [Container](./other-plugins/container.md)
* [Exec (local scripts)](./other-plugins/exec.md)

## 🌷 Guides
## 🪷 Advanced

* [Using Remote Sources](./advanced/using-remote-sources.md)
* [Custom Commands](./advanced/custom-commands.md)

## 🎋 Guides

* [Installing Garden](./guides/installation.md)
* [Adopting Garden](./guides/adopting-garden.md)
Expand All @@ -92,12 +101,7 @@
* [Migrating from Docker Compose to Garden](./guides/migrating-from-docker-compose.md)
* [Using Garden in CI](./guides/using-garden-in-ci.md)

## 🪷 Advanced

* [Using Remote Sources](./advanced/using-remote-sources.md)
* [Custom Commands](./advanced/custom-commands.md)

## 🎋 Reference
## undefined Reference

* [Providers](./reference/providers/README.md)
* [`conftest-container`](./reference/providers/conftest-container.md)
Expand Down
6 changes: 6 additions & 0 deletions docs/docker-compose-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Docker Compose Plugin
order: 8
---

* Plugin command: `compose`
118 changes: 118 additions & 0 deletions docs/docker-compose-plugin/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
order: 1
title: About
---

# About

{% hint style="warning" %}
The Docker Compose plugin is still experimental. Please let us know if you have any questions or if any issues come up!
{% endhint %}

This plugin allows you to integrate [Docker Compose](https://docs.docker.com/compose/) projects into your Garden project.

It works by parsing the Docker Compose projects, and creating Build and Deploy actions for each [service](https://docs.docker.com/compose/compose-file/05-services/) in the project.

You can then easily add Run and Test actions to complement your Compose project.

This can be very useful e.g. for running tests against a Docker Compose stack in CI (and locally), and to wrap various scripts you use during development (e.g. a Run for seeding a database with test data, or a Run for generating a database migration inside a container that you're developing).

The provided action types are (links point to the corresponding reference docs):
* `docker-compose-service` ([Build](../reference/action-types/Build/docker-compose-service.md) and [Deploy](../reference/action-types/Deploy/docker-compose-service.md)): These wrap the build and deploy (service) steps defined in a Compose project.
* The `docker-compose-service` Build action calls `docker compose build <service-name>` under the hood.
* The `docker-compose-service` Deploy action calls `docker compose up <service-name>` under the hood.
* `docker-compose-exec` ([Run](../reference/action-types/Run/docker-compose-exec.md) and [Test](../reference/action-types/Test/docker-compose-exec.md)): These use `docker compose exec` to execute the specified command in an already running Docker Compose service.
* `docker-compose-run` ([Run](../reference/action-types/Run/docker-compose-run.md) and [Test](../reference/action-types/Test/docker-compose-run.md)): These use `docker compose run` to run the specified command in a new container based on the Docker Compose service.
* `docker-run` ([Run](../reference/action-types/Run/docker-run.md) and [Test](../reference/action-types/Test/docker-run.md)): Like `docker-compose-run`, but these are independent of the Docker Compose project, and simply reference a Docker image tag to run (uses `docker run` under the hood).

## Getting started

First, add the `docker-compose` provider to your Garden project configuration. Here's a minimal example:
```yaml
apiVersion: garden.io/v1
kind: Project
name: my-project
environments:
- name: local
providers:
- name: docker-compose
environments: [local]
```
{% hint style="info" %}
Note that you can easily combine the `docker-compose` provider with the `local-kubernetes` and `kubernetes` providers: A typical use-case would be to use Docker Compose for local development, and then the Kubernetes-based plugins in CI (or when you want to reproduce & debug failing tests in CI from your dev machine without re-running the pipeline).

Your Docker Compose actions can live side by side with your Kubernetes-based actions.
{% endhint %}

That's all you need to build and deploy your existing Compose project with Garden! Just run `garden deploy` to deploy your project, `garden logs -f` to tail the logs etc.

## Extending your Docker Compose project with tests and scripts

In vanilla Compose, you might set up a dummy service to wrap a script:
```yaml
# From https://github.com/garden-io/garden/blob/main/examples/docker-compose/docker-compose.yml
...
# this service runs once to seed the database with votes
# it won't run unless you specify the "seed" profile
# docker compose --profile seed up -d
seed:
build: ./seed-data
profiles: ["seed"]
depends_on:
vote:
condition: service_healthy
networks:
- front-tier
restart: "no"
```
You could do the same for tests (set them up as services and run them as one-off containers using special profiles).

While this works, it's a bit clunky—we're making use of the service primitive to run scripts and tests, which isn't what it was really designed for (building and running a service).

Fortunately, Garden's Run and Test actions are a more natural fit for this. This makes Garden's Docker Compose plugin the perfect extension to Docker Compose. This is how we'd seed the DB with a Run:
```yaml
# From https://github.com/garden-io/garden/blob/main/examples/docker-compose/project.garden.yml
kind: Run
type: docker-run
name: seed-votes
description: |
Seed Postgres with some test data. We don't use the seed service in the compose project here, but reference
an image from a `container` Build instead.
dependencies: [deploy.vote-compose]
spec:
projectName: ${var.projectName}
image: ${actions.build.seed-votes.outputs.deployment-image-id}
networks: ["front-tier"]
```
Instead of `docker compose --profile seed up -d`, we can now just run `garden run seed-votes`.

Tests are very similar:
```yaml
# From https://github.com/garden-io/garden/blob/main/examples/docker-compose/project.garden.yml
kind: Test
type: docker-run
name: vote-integ
dependencies: [deploy.vote-compose]
spec:
# The build action here is located in ./result/tests/build.garden.yml
projectName: ${var.projectName}
image: ${actions.build.vote-tests.outputs.deployment-image-id}
networks: [front-tier]
```
and are run via `garden test`.


You can also take advantage of Garden's variables and templating to provide environment variable overrides to the actions you define, and more. See the reference docs for the full set of config fields available (links for each action type provided by this plugin can be found in the [About](#about) section above).

## Bringing together several Docker Compose projects

This is an advanced use-case supported by the plugin. Using the [`projects`](../reference/providers/docker-compose.md/#providersprojects) field on the `docker-compose` provider, you can specify several projects to be included in the Garden project (by default, Garden looks for a Docker Compose project in the same directory as the Garden project configuration).

This can be useful e.g. for end-to-end testing in CI where you want to tie together several Docker Compose projects for a more complete stack.

## Next steps

The simplest way to take this plugin for a spin is to try the [`docker-compose` example project](../../examples/docker-compose), where you can see most of the action types and features of the plugin in action.

If you're having issues with Docker Compose itself, please refer to the [official docs](https://docs.docker.com/compose/).
2 changes: 1 addition & 1 deletion docs/guides/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
---
order: 9
order: 10
title: Guides
---
2 changes: 1 addition & 1 deletion docs/other-plugins/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
---
title: Other Plugins
order: 8
order: 9
---
6 changes: 3 additions & 3 deletions docs/reference/action-types/Build/docker-compose-service.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ tocTitle: "`docker-compose-service` Build"

## Description

TODO
Uses `docker compose build` to build a service in a Docker Compose project.

Below is the full schema reference for the action. For an introduction to configuring Garden, please look at our [Configuration
guide](../../../using-garden/configuration-overview.md).
Expand Down Expand Up @@ -180,7 +180,7 @@ exclude:
timeout: 600

spec:
# The Compose project name, as specified in the provider configuration.
# The Compose project name. This field is usually unnecessary unless using several Compose projects together.
projectName:

# The name of the service to build.
Expand Down Expand Up @@ -465,7 +465,7 @@ Set a timeout for the build to complete, in seconds.

[spec](#spec) > projectName

The Compose project name, as specified in the provider configuration.
The Compose project name. This field is usually unnecessary unless using several Compose projects together.

| Type | Required |
| -------- | -------- |
Expand Down
6 changes: 3 additions & 3 deletions docs/reference/action-types/Deploy/docker-compose-service.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ tocTitle: "`docker-compose-service` Deploy"

## Description

TODO
Uses `docker compose up` to deploy a single service in a Docker Compose project.

Below is the full schema reference for the action. For an introduction to configuring Garden, please look at our [Configuration
guide](../../../using-garden/configuration-overview.md).
Expand Down Expand Up @@ -151,7 +151,7 @@ kind:
timeout: 300

spec:
# The Compose project name, as specified in the provider configuration.
# The Compose project name. This field is usually unnecessary unless using several Compose projects together.
projectName:

# The name of the service to deploy.
Expand Down Expand Up @@ -387,7 +387,7 @@ Timeout for the deploy to complete, in seconds.

[spec](#spec) > projectName

The Compose project name, as specified in the provider configuration.
The Compose project name. This field is usually unnecessary unless using several Compose projects together.

| Type | Required |
| -------- | -------- |
Expand Down
39 changes: 27 additions & 12 deletions docs/reference/action-types/Run/docker-compose-exec.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,12 @@ kind:
timeout: 600

spec:
# The Compose project name, as specified in the provider configuration.
# The Compose project name. This field is usually unnecessary unless using several Compose projects together.
projectName:

# The name of the service.
service:

command:

# Environment variables to set during execution.
env: {}

Expand All @@ -168,6 +166,15 @@ spec:
# Path to workdir directory for this command.
workdir:

# The command to run inside the container. Note that there's no entrypoint on this schema: When we exec into
# a running container, there's no need to override the image's entrypoint.
#
# This field maps to the `COMMAND` that's passed to `docker compose exec`. See
# https://docs.docker.com/engine/reference/commandline/exec/#description for more info.
#
# Example: `["echo","Hello World"]`
command:

# Index of the container if there are multiple instances of a service.
index: 1

Expand Down Expand Up @@ -404,7 +411,7 @@ Set a timeout for the run to complete, in seconds.

[spec](#spec) > projectName

The Compose project name, as specified in the provider configuration.
The Compose project name. This field is usually unnecessary unless using several Compose projects together.

| Type | Required |
| -------- | -------- |
Expand All @@ -420,14 +427,6 @@ The name of the service.
| -------- | -------- |
| `string` | Yes |

### `spec.command[]`

[spec](#spec) > command

| Type | Required |
| ------- | -------- |
| `array` | Yes |

### `spec.env`

[spec](#spec) > env
Expand Down Expand Up @@ -458,6 +457,22 @@ Path to workdir directory for this command.
| -------- | -------- |
| `string` | No |

### `spec.command[]`

[spec](#spec) > command

The command to run inside the container. Note that there's no entrypoint on this schema: When we exec into
a running container, there's no need to override the image's entrypoint.

This field maps to the `COMMAND` that's passed to `docker compose exec`. See
https://docs.docker.com/engine/reference/commandline/exec/#description for more info.

Example: `["echo","Hello World"]`

| Type | Required |
| ------- | -------- |
| `array` | Yes |

### `spec.index`

[spec](#spec) > index
Expand Down
Loading

0 comments on commit d8e43ca

Please sign in to comment.