Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit f339ae6

Browse files
committed
Merge pull request #136 from vdemeester/no-build-support
Add support for --no-build
2 parents 3113d86 + 89a14b9 commit f339ae6

File tree

8 files changed

+102
-31
lines changed

8 files changed

+102
-31
lines changed

cli/command/command.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ func CreateCommand(factory app.ProjectFactory) cli.Command {
2323
Name: "force-recreate",
2424
Usage: "Recreate containers even if their configuration and image haven't changed. Incompatible with --no-recreate.",
2525
},
26+
cli.BoolFlag{
27+
Name: "no-build",
28+
Usage: "Don't build an image, even if it's missing.",
29+
},
2630
},
2731
}
2832
}
@@ -89,6 +93,10 @@ func UpCommand(factory app.ProjectFactory) cli.Command {
8993
Name: "d",
9094
Usage: "Do not block and log",
9195
},
96+
cli.BoolFlag{
97+
Name: "no-build",
98+
Usage: "Don't build an image, even if it's missing.",
99+
},
92100
cli.BoolFlag{
93101
Name: "no-recreate",
94102
Usage: "If containers already exist, don't recreate them. Incompatible with --force-recreate.",
@@ -283,6 +291,7 @@ func Populate(context *project.Context, c *cli.Context) {
283291
context.Log = !c.Bool("d")
284292
context.NoRecreate = c.Bool("no-recreate")
285293
context.ForceRecreate = c.Bool("force-recreate")
294+
context.NoBuild = c.Bool("no-build")
286295
} else if c.Command.Name == "stop" || c.Command.Name == "restart" || c.Command.Name == "scale" {
287296
context.Timeout = uint(c.Int("timeout"))
288297
} else if c.Command.Name == "kill" {

docker/builder.go

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const DefaultDockerfileName = "Dockerfile"
2222
// Builder defines methods to provide a docker builder. This makes libcompose
2323
// not tied up to the docker daemon builder.
2424
type Builder interface {
25-
Build(p *project.Project, service project.Service) (string, error)
25+
Build(imageName string, p *project.Project, service project.Service) error
2626
}
2727

2828
// DaemonBuilder is the daemon "docker build" Builder implementation.
@@ -39,38 +39,31 @@ func NewDaemonBuilder(context *Context) *DaemonBuilder {
3939

4040
// Build implements Builder. It consumes the docker build API endpoint and sends
4141
// a tar of the specified service build context.
42-
func (d *DaemonBuilder) Build(p *project.Project, service project.Service) (string, error) {
42+
func (d *DaemonBuilder) Build(imageName string, p *project.Project, service project.Service) error {
4343
if service.Config().Build == "" {
44-
return service.Config().Image, nil
44+
return fmt.Errorf("Specified service does not have a build section")
4545
}
4646

47-
tag := fmt.Sprintf("%s_%s", p.Name, service.Name())
4847
context, err := CreateTar(p, service.Name())
4948
if err != nil {
50-
return "", err
49+
return err
5150
}
5251

5352
defer context.Close()
5453

5554
client := d.context.ClientFactory.Create(service)
5655

57-
logrus.Infof("Building %s...", tag)
56+
logrus.Infof("Building %s...", imageName)
5857

59-
err = client.BuildImage(dockerclient.BuildImageOptions{
58+
return client.BuildImage(dockerclient.BuildImageOptions{
6059
InputStream: context,
6160
OutputStream: os.Stdout,
6261
RawJSONStream: false,
63-
Name: tag,
62+
Name: imageName,
6463
RmTmpContainer: true,
6564
Dockerfile: service.Config().Dockerfile,
6665
NoCache: d.context.NoCache,
6766
})
68-
69-
if err != nil {
70-
return "", err
71-
}
72-
73-
return tag, nil
7467
}
7568

7669
// CreateTar create a build context tar for the specified project and service name.

docker/service.go

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package docker
22

33
import (
44
"fmt"
5+
56
"github.com/Sirupsen/logrus"
67
"github.com/docker/docker/pkg/nat"
78
"github.com/docker/libcompose/project"
89
"github.com/docker/libcompose/utils"
10+
"github.com/fsouza/go-dockerclient"
911
)
1012

1113
// Service is a project.Service implementations.
@@ -39,14 +41,15 @@ func (s *Service) DependentServices() []project.ServiceRelationship {
3941
return project.DefaultDependentServices(s.context.Project, s)
4042
}
4143

42-
// Create implements Service.Create.
44+
// Create implements Service.Create. It ensures the image exists or build it
45+
// if it can and then create a container.
4346
func (s *Service) Create() error {
4447
containers, err := s.collectContainers()
4548
if err != nil {
4649
return err
4750
}
4851

49-
imageName, err := s.build()
52+
imageName, err := s.ensureImageExists()
5053
if err != nil {
5154
return err
5255
}
@@ -87,20 +90,57 @@ func (s *Service) createOne(imageName string) (*Container, error) {
8790
return containers[0], err
8891
}
8992

93+
func (s *Service) ensureImageExists() (string, error) {
94+
err := s.imageExists()
95+
96+
if err == nil {
97+
return s.imageName(), nil
98+
}
99+
100+
if err != nil && err != docker.ErrNoSuchImage {
101+
return "", err
102+
}
103+
104+
if s.Config().Build != "" {
105+
if s.context.NoBuild {
106+
return "", fmt.Errorf("Service %q needs to be built, but no-build was specified", s.name)
107+
}
108+
return s.imageName(), s.build()
109+
}
110+
111+
return s.imageName(), s.Pull()
112+
}
113+
114+
func (s *Service) imageExists() error {
115+
client := s.context.ClientFactory.Create(s)
116+
117+
_, err := client.InspectImage(s.imageName())
118+
return err
119+
}
120+
121+
func (s *Service) imageName() string {
122+
if s.Config().Image != "" {
123+
return s.Config().Image
124+
}
125+
return fmt.Sprintf("%s_%s", s.context.ProjectName, s.Name())
126+
}
127+
90128
// Build implements Service.Build. If an imageName is specified or if the context has
91129
// no build to work with it will do nothing. Otherwise it will try to build
92130
// the image and returns an error if any.
93131
func (s *Service) Build() error {
94-
_, err := s.build()
95-
return err
132+
if s.Config().Image != "" {
133+
return nil
134+
}
135+
return s.build()
96136
}
97137

98-
func (s *Service) build() (string, error) {
138+
func (s *Service) build() error {
99139
if s.context.Builder == nil {
100-
return s.Config().Image, nil
140+
return fmt.Errorf("Cannot build an image without a builder configured")
101141
}
102142

103-
return s.context.Builder.Build(s.context.Project, s)
143+
return s.context.Builder.Build(s.imageName(), s.context.Project, s)
104144
}
105145

106146
func (s *Service) constructContainers(imageName string, count int) ([]*Container, error) {
@@ -145,11 +185,19 @@ func (s *Service) constructContainers(imageName string, count int) ([]*Container
145185
// Up implements Service.Up. It builds the image if needed, creates a container
146186
// and start it.
147187
func (s *Service) Up() error {
148-
imageName, err := s.build()
188+
containers, err := s.collectContainers()
149189
if err != nil {
150190
return err
151191
}
152192

193+
var imageName = s.imageName()
194+
if len(containers) == 0 || !s.context.NoRecreate {
195+
imageName, err = s.ensureImageExists()
196+
if err != nil {
197+
return err
198+
}
199+
}
200+
153201
return s.up(imageName, true)
154202
}
155203

@@ -310,7 +358,7 @@ func (s *Service) Scale(scale int) error {
310358
}
311359

312360
if foundCount != scale {
313-
imageName, err := s.build()
361+
imageName, err := s.ensureImageExists()
314362
if err != nil {
315363
return err
316364
}
@@ -323,7 +371,8 @@ func (s *Service) Scale(scale int) error {
323371
return s.up("", false)
324372
}
325373

326-
// Pull implements Service.Pull. It pulls or build the image of the service.
374+
// Pull implements Service.Pull. It pulls the image of the service and skip the service that
375+
// would need to be built.
327376
func (s *Service) Pull() error {
328377
if s.Config().Image == "" {
329378
return nil
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
one:
2+
build: one
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FROM busybox
2+
CMD ["echo", "one"]

integration/build_test.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package integration
22

33
import (
44
"fmt"
5-
"os"
65
"os/exec"
76
"strings"
87

@@ -12,9 +11,6 @@ import (
1211
func (s *RunSuite) TestBuild(c *C) {
1312
p := s.RandomProject()
1413
cmd := exec.Command(s.command, "-f", "./assets/build/docker-compose.yml", "-p", p, "build")
15-
cmd.Stdout = os.Stdout
16-
cmd.Stderr = os.Stderr
17-
cmd.Stdin = os.Stdin
1814
err := cmd.Run()
1915

2016
oneImageName := fmt.Sprintf("%s_one", p)
@@ -67,9 +63,6 @@ func (s *RunSuite) TestBuildWithNoCache2(c *C) {
6763
func (s *RunSuite) TestBuildWithNoCache3(c *C) {
6864
p := s.RandomProject()
6965
cmd := exec.Command(s.command, "-f", "./assets/build/docker-compose.yml", "-p", p, "build", "--no-cache")
70-
cmd.Stdout = os.Stdout
71-
cmd.Stderr = os.Stderr
72-
cmd.Stdin = os.Stdin
7366
err := cmd.Run()
7467

7568
oneImageName := fmt.Sprintf("%s_one", p)

integration/up_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package integration
22

33
import (
44
"fmt"
5+
"os/exec"
56
"path/filepath"
67
"strings"
78

@@ -297,3 +298,24 @@ func (s *RunSuite) TestRelativeVolume(c *C) {
297298
c.Assert(len(cn.Mounts), DeepEquals, 1)
298299
c.Assert(cn.Mounts[0].Source, DeepEquals, absPath)
299300
}
301+
302+
func (s *RunSuite) TestUpNoBuildFailIfImageNotPresent(c *C) {
303+
p := s.RandomProject()
304+
cmd := exec.Command(s.command, "-f", "./assets/build/docker-compose.yml", "-p", p, "up", "--no-build")
305+
err := cmd.Run()
306+
307+
c.Assert(err, NotNil)
308+
}
309+
310+
func (s *RunSuite) TestUpNoBuildShouldWorkIfImageIsPresent(c *C) {
311+
p := s.RandomProject()
312+
cmd := exec.Command(s.command, "-f", "./assets/simple-build/docker-compose.yml", "-p", p, "build")
313+
err := cmd.Run()
314+
315+
c.Assert(err, IsNil)
316+
317+
cmd = exec.Command(s.command, "-f", "./assets/simple-build/docker-compose.yml", "-p", p, "up", "-d", "--no-build")
318+
err = cmd.Run()
319+
320+
c.Assert(err, IsNil)
321+
}

project/context.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type Context struct {
2424
ForceRecreate bool
2525
NoRecreate bool
2626
NoCache bool
27+
NoBuild bool
2728
Signal int
2829
ComposeFiles []string
2930
ComposeBytes [][]byte

0 commit comments

Comments
 (0)