Skip to content

Commit d57846d

Browse files
authored
Merge pull request #174 from AkihiroSuda/dev-compose
compose: support build
2 parents 8952cd7 + 2b9a8a9 commit d57846d

16 files changed

+439
-20
lines changed

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ It does not necessarily mean that the corresponding features are missing in cont
221221
- [:whale: nerdctl compose](#whale-nerdctl-compose)
222222
- [:whale: nerdctl compose up](#whale-nerdctl-compose-up)
223223
- [:whale: nerdctl compose logs](#whale-nerdctl-compose-logs)
224+
- [:whale: nerdctl compose build](#whale-nerdctl-compose-build)
224225
- [:whale: nerdctl compose down](#whale-nerdctl-compose-down)
225226
- [Global flags](#global-flags)
226227
- [Unimplemented Docker commands](#unimplemented-docker-commands)
@@ -777,9 +778,10 @@ Flags:
777778
- :whale: `-d, --detach`: Detached mode: Run containers in the background
778779
- :whale: `--no-color`: Produce monochrome output
779780
- :whale: `--no-log-prefix`: Don't print prefix in logs
781+
- :whale: `build`: Build images before starting containers.
780782

781783
Unimplemented `docker-compose up` flags: `--quiet-pull`, `--no-deps`, `--force-recreate`, `--always-recreate-deps`, `--no-recreate`,
782-
`--no-start`, `--build`, `--abort-on-container-exit`, `--attach-dependencies`, `--timeout`, `--renew-anon-volumes`, `--remove-orphans`, `--exit-code-from`,
784+
`--no-start`, `--abort-on-container-exit`, `--attach-dependencies`, `--timeout`, `--renew-anon-volumes`, `--remove-orphans`, `--exit-code-from`,
783785
`--scale`
784786

785787
### :whale: nerdctl compose logs
@@ -793,6 +795,18 @@ Flags:
793795

794796
Unimplemented `docker-compose logs` flags: `--timestamps`, `--tail`
795797

798+
### :whale: nerdctl compose build
799+
Build or rebuild services.
800+
801+
Usage: `nerdctl compose build [OPTIONS]`
802+
803+
Flags:
804+
- :whale: `--build-arg`: Set build-time variables for services
805+
- :whale: `--no-cache`: Do not use cache when building the image
806+
- :whale: `--progress`: Set type of progress output (auto, plain, tty)
807+
808+
Unimplemented `docker-compose build` flags: `--compress`, `--force-rm`, `--memory`, `--no-rm`, `--parallel`, `--pull`, `--quiet`
809+
796810
### :whale: nerdctl compose down
797811
Remove containers and associated resources
798812

@@ -850,7 +864,7 @@ Registry:
850864
- `docker search`
851865

852866
Compose:
853-
- `docker-compose build|config|create|events|exec|images|kill|logs|pause|port|ps|pull|push|restart|rm|run|scale|start|stop|top|unpause`
867+
- `docker-compose config|create|events|exec|images|kill|pause|port|ps|pull|push|restart|rm|run|scale|start|stop|top|unpause`
854868

855869
Others:
856870
- `docker system df`

compose.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
"github.com/containerd/containerd"
2323
"github.com/containerd/containerd/errdefs"
24+
refdocker "github.com/containerd/containerd/reference/docker"
2425
"github.com/containerd/nerdctl/pkg/composer"
2526
"github.com/containerd/nerdctl/pkg/imgutil"
2627
"github.com/containerd/nerdctl/pkg/netutil"
@@ -34,6 +35,7 @@ var composeCommand = &cli.Command{
3435
Subcommands: []*cli.Command{
3536
composeUpCommand,
3637
composeLogsCommand,
38+
composeBuildCommand,
3739
composeDownCommand,
3840
},
3941

@@ -94,6 +96,21 @@ func getComposer(clicontext *cli.Context, client *containerd.Client) (*composer.
9496
}
9597
}
9698

99+
o.ImageExists = func(ctx context.Context, rawRef string) (bool, error) {
100+
named, err := refdocker.ParseDockerRef(rawRef)
101+
if err != nil {
102+
return false, err
103+
}
104+
ref := named.String()
105+
if _, err := client.ImageService().Get(ctx, ref); err != nil {
106+
if errors.Is(err, errdefs.ErrNotFound) {
107+
return false, nil
108+
}
109+
return false, err
110+
}
111+
return true, nil
112+
}
113+
97114
insecure := clicontext.Bool("insecure-registry")
98115
o.EnsureImage = func(ctx context.Context, imageName, pullMode string) error {
99116
_, imgErr := imgutil.EnsureImage(ctx, client, clicontext.App.Writer, clicontext.String("snapshotter"), imageName,

compose_build.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"github.com/containerd/nerdctl/pkg/composer"
21+
"github.com/pkg/errors"
22+
"github.com/urfave/cli/v2"
23+
)
24+
25+
var composeBuildCommand = &cli.Command{
26+
Name: "build",
27+
Usage: "Build or rebuild services",
28+
Action: composeBuildAction,
29+
Flags: []cli.Flag{
30+
&cli.StringSliceFlag{
31+
Name: "build-arg",
32+
Usage: "Set build-time variables for services.",
33+
},
34+
&cli.BoolFlag{
35+
Name: "no-cache",
36+
Usage: "Do not use cache when building the image.",
37+
},
38+
&cli.StringFlag{
39+
Name: "progress",
40+
Usage: "Set type of progress output",
41+
},
42+
},
43+
}
44+
45+
func composeBuildAction(clicontext *cli.Context) error {
46+
if clicontext.NArg() != 0 {
47+
// TODO: support specifying service names as args
48+
return errors.Errorf("arguments %v not supported", clicontext.Args())
49+
}
50+
51+
client, ctx, cancel, err := newClient(clicontext)
52+
if err != nil {
53+
return err
54+
}
55+
defer cancel()
56+
57+
c, err := getComposer(clicontext, client)
58+
if err != nil {
59+
return err
60+
}
61+
bo := composer.BuildOptions{
62+
Args: clicontext.StringSlice("build-arg"),
63+
NoCache: clicontext.Bool("no-cache"),
64+
Progress: clicontext.String("progress"),
65+
}
66+
return c.Build(ctx, bo)
67+
}

compose_up.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ var composeUpCommand = &cli.Command{
3939
Name: "no-log-prefix",
4040
Usage: "Don't print prefix in logs",
4141
},
42+
&cli.BoolFlag{
43+
Name: "build",
44+
Usage: "Build images before starting containers.",
45+
},
4246
},
4347
}
4448

@@ -57,6 +61,7 @@ func composeUpAction(clicontext *cli.Context) error {
5761
Detach: clicontext.Bool("detach"),
5862
NoColor: clicontext.Bool("no-color"),
5963
NoLogPrefix: clicontext.Bool("no-log-prefix"),
64+
ForceBuild: clicontext.Bool("build"),
6065
}
6166
return c.Up(ctx, uo)
6267
}

compose_up_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525

2626
"github.com/containerd/nerdctl/pkg/testutil"
2727
"github.com/pkg/errors"
28+
"gotest.tools/v3/assert"
2829
)
2930

3031
func TestComposeUp(t *testing.T) {
@@ -112,3 +113,36 @@ volumes:
112113
base.Cmd("volume", "inspect", fmt.Sprintf("%s_db", projectName)).AssertFail()
113114
base.Cmd("network", "inspect", fmt.Sprintf("%s_default", projectName)).AssertFail()
114115
}
116+
117+
func TestComposeUpBuild(t *testing.T) {
118+
testutil.RequiresBuild(t)
119+
base := testutil.NewBase(t)
120+
121+
const dockerComposeYAML = `
122+
services:
123+
web:
124+
build: .
125+
ports:
126+
- 8080:80
127+
`
128+
dockerfile := fmt.Sprintf(`FROM %s
129+
COPY index.html /usr/share/nginx/html/index.html
130+
`, testutil.NginxAlpineImage)
131+
indexHTML := t.Name()
132+
133+
comp := testutil.NewComposeDir(t, dockerComposeYAML)
134+
defer comp.CleanUp()
135+
136+
comp.WriteFile("Dockerfile", dockerfile)
137+
comp.WriteFile("index.html", indexHTML)
138+
139+
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "-d", "--build").AssertOK()
140+
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()
141+
142+
resp, err := httpGet("http://127.0.0.1:8080", 50)
143+
assert.NilError(t, err)
144+
respBody, err := ioutil.ReadAll(resp.Body)
145+
assert.NilError(t, err)
146+
t.Logf("respBody=%q", respBody)
147+
assert.Assert(t, strings.Contains(string(respBody), indexHTML))
148+
}

docs/compose.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ which was derived from [Docker Compose file version 3 specification](https://doc
1818

1919
### Unimplemented YAML fields
2020
- Fields that correspond to unimplemented `docker run` flags, e.g., `services.<SERVICE>.links` (corresponds to `docker run --link`)
21-
- `services.<SERVICE>.build`
21+
- Fields that correspond to unimplemented `docker build` flags, e.g., `services.<SERVICE>.build.labels` (corresponds to `docker build --label`)
2222
- `services.<SERVICE>.credential_spec`
2323
- `services.<SERVICE>.deploy.update_config`
2424
- `services.<SERVICE>.deploy.rollback_config`
@@ -33,6 +33,9 @@ which was derived from [Docker Compose file version 3 specification](https://doc
3333
- `secrets.<SECRET>.external`
3434

3535
### Incompatibility
36+
#### `services.<SERVICE>.build.context`
37+
- The value must be a local directory path, not a URL.
38+
3639
#### `services.<SERVICE>.entrypoint`
3740
- Multiple entrypoint strings cannot be specified.
3841

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/containerd/nerdctl
33
go 1.16
44

55
require (
6-
github.com/compose-spec/compose-go v0.0.0-20210408102153-fe76f8471db2
6+
github.com/compose-spec/compose-go v0.0.0-20210415072938-109132373677
77
github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68
88
github.com/containerd/console v1.0.2
99
github.com/containerd/containerd v1.5.0-rc.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
9797
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
9898
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
9999
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
100-
github.com/compose-spec/compose-go v0.0.0-20210408102153-fe76f8471db2 h1:z1lMW1kukwXXuTxwqXEZUuNrkTb7NA/gnpcsEtYpTsQ=
101-
github.com/compose-spec/compose-go v0.0.0-20210408102153-fe76f8471db2/go.mod h1:6eIT9U2OgdHmkRD6szmqatCrWWEEUSwl/j2iJYH4jLo=
100+
github.com/compose-spec/compose-go v0.0.0-20210415072938-109132373677 h1:WXbccjCgmE1RtrWGbtlLz6NF4VMIZrXsM+FX+UzFmd0=
101+
github.com/compose-spec/compose-go v0.0.0-20210415072938-109132373677/go.mod h1:6eIT9U2OgdHmkRD6szmqatCrWWEEUSwl/j2iJYH4jLo=
102102
github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
103103
github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
104104
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=

pkg/composer/build.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package composer
18+
19+
import (
20+
"context"
21+
"os"
22+
23+
"github.com/compose-spec/compose-go/types"
24+
"github.com/containerd/nerdctl/pkg/composer/serviceparser"
25+
"github.com/pkg/errors"
26+
"github.com/sirupsen/logrus"
27+
)
28+
29+
type BuildOptions struct {
30+
Args []string // --build-arg strings
31+
NoCache bool
32+
Progress string
33+
}
34+
35+
func (c *Composer) Build(ctx context.Context, bo BuildOptions) error {
36+
return c.project.WithServices(nil, func(svc types.ServiceConfig) error {
37+
ps, err := serviceparser.Parse(c.project, svc)
38+
if err != nil {
39+
return err
40+
}
41+
if ps.Build != nil {
42+
return c.buildServiceImage(ctx, ps.Image, ps.Build, bo)
43+
}
44+
return nil
45+
})
46+
}
47+
48+
func (c *Composer) buildServiceImage(ctx context.Context, image string, b *serviceparser.Build, bo BuildOptions) error {
49+
logrus.Infof("Building image %s", image)
50+
51+
var args []string // nolint: prealloc
52+
for _, a := range bo.Args {
53+
args = append(args, "--build-arg="+a)
54+
}
55+
if bo.NoCache {
56+
args = append(args, "--no-cache")
57+
}
58+
if bo.Progress != "" {
59+
args = append(args, "--progress="+bo.Progress)
60+
}
61+
args = append(args, b.BuildArgs...)
62+
63+
cmd := c.createNerdctlCmd(ctx, append([]string{"build"}, args...)...)
64+
if c.DebugPrintFull {
65+
logrus.Debugf("Running %v", cmd.Args)
66+
}
67+
cmd.Stdout = os.Stdout
68+
cmd.Stderr = os.Stderr
69+
if err := cmd.Run(); err != nil {
70+
return errors.Wrapf(err, "error while building image %s", image)
71+
}
72+
return nil
73+
}

pkg/composer/composer.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type Options struct {
3838
NerdctlArgs []string
3939
NetworkExists func(string) (bool, error)
4040
VolumeExists func(string) (bool, error)
41+
ImageExists func(ctx context.Context, imageName string) (bool, error)
4142
EnsureImage func(ctx context.Context, imageName, pullMode string) error
4243
DebugPrintFull bool // full debug print, may leak secret env var to logs
4344
}

0 commit comments

Comments
 (0)