Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.
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
16 changes: 16 additions & 0 deletions cli/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,22 @@ func ProjectKill(p *project.Project, c *cli.Context) {
}
}

// ProjectPause pauses service containers.
func ProjectPause(p *project.Project, c *cli.Context) {
err := p.Pause(c.Args()...)
if err != nil {
logrus.Fatal(err)
}
}

// ProjectUnpause unpauses service containers.
func ProjectUnpause(p *project.Project, c *cli.Context) {
err := p.Unpause(c.Args()...)
if err != nil {
logrus.Fatal(err)
}
}

// ProjectScale scales services.
func ProjectScale(p *project.Project, c *cli.Context) {
// This code is a bit verbose but I wanted to parse everything up front
Expand Down
20 changes: 20 additions & 0 deletions cli/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,26 @@ func KillCommand(factory app.ProjectFactory) cli.Command {
}
}

// PauseCommand defines the libcompose pause subcommand.
func PauseCommand(factory app.ProjectFactory) cli.Command {
return cli.Command{
Name: "pause",
Usage: "Pause services.",
// ArgsUsage: "[SERVICE...]",
Action: app.WithProject(factory, app.ProjectPause),
}
}

// UnpauseCommand defines the libcompose unpause subcommand.
func UnpauseCommand(factory app.ProjectFactory) cli.Command {
return cli.Command{
Name: "unpause",
Usage: "Unpause services.",
// ArgsUsage: "[SERVICE...]",
Action: app.WithProject(factory, app.ProjectUnpause),
}
}

// CommonFlags defines the flags that are in common for all subcommands.
func CommonFlags() []cli.Flag {
return []cli.Flag{
Expand Down
2 changes: 2 additions & 0 deletions cli/main/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ func main() {
command.KillCommand(factory),
command.PortCommand(factory),
command.PsCommand(factory),
command.PauseCommand(factory),
command.UnpauseCommand(factory),
}

app.Run(os.Args)
Expand Down
20 changes: 20 additions & 0 deletions docker/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,26 @@ func (c *Container) Down() error {
})
}

// Pause pauses the container. If the containers are already paused, don't fail.
func (c *Container) Pause() error {
return c.withContainer(func(container *dockerclient.APIContainers) error {
if !strings.Contains(container.Status, "Paused") {
return c.client.PauseContainer(container.ID)
}
return nil
})
}

// Unpause unpauses the container. If the containers are not paused, don't fail.
func (c *Container) Unpause() error {
return c.withContainer(func(container *dockerclient.APIContainers) error {
if strings.Contains(container.Status, "Paused") {
return c.client.UnpauseContainer(container.ID)
}
return nil
})
}

// Kill kill the container.
func (c *Container) Kill() error {
return c.withContainer(func(container *dockerclient.APIContainers) error {
Expand Down
16 changes: 16 additions & 0 deletions docker/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,22 @@ func (s *Service) Pull() error {
return pullImage(s.context.ClientFactory.Create(s), s, s.Config().Image)
}

// Pause implements Service.Pause. It puts into pause the container(s) related
// to the service.
func (s *Service) Pause() error {
return s.eachContainer(func(c *Container) error {
return c.Pause()
})
}

// Unpause implements Service.Pause. It brings back from pause the container(s)
// related to the service.
func (s *Service) Unpause() error {
return s.eachContainer(func(c *Container) error {
return c.Unpause()
})
}

// Containers implements Service.Containers. It returns the list of containers
// that are related to the service.
func (s *Service) Containers() ([]project.Container, error) {
Expand Down
3 changes: 3 additions & 0 deletions integration/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ func (s *RunSuite) TearDownTest(c *C) {
containers, err := client.ListContainers(dockerclient.ListContainersOptions{All: true})
c.Assert(err, IsNil)
for _, container := range containers {
// Unpause container (if paused) and ignore error (if wasn't paused)
client.UnpauseContainer(container.ID)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As commented, this is to make sure we won't fail in TearDown because of a paused container… So we're trying to unpause all of them and move on 😉.

// And remove force \o/
err := client.RemoveContainer(dockerclient.RemoveContainerOptions{ID: container.ID, Force: true, RemoveVolumes: true})
c.Assert(err, IsNil)
}
Expand Down
92 changes: 92 additions & 0 deletions integration/pause_unpause_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package integration

import (
"fmt"

. "gopkg.in/check.v1"
)

func (s *RunSuite) TestPause(c *C) {
p := s.ProjectFromText(c, "up", SimpleTemplate)

name := fmt.Sprintf("%s_%s_1", p, "hello")
cn := s.GetContainerByName(c, name)
c.Assert(cn, NotNil)
c.Assert(cn.State.Running, Equals, true)
c.Assert(cn.State.Paused, Equals, false)

s.FromText(c, p, "pause", SimpleTemplate)

cn = s.GetContainerByName(c, name)
c.Assert(cn, NotNil)
c.Assert(cn.State.Running, Equals, true)
c.Assert(cn.State.Paused, Equals, true)
}

func (s *RunSuite) TestPauseAlreadyPausedService(c *C) {
p := s.ProjectFromText(c, "up", SimpleTemplate)

name := fmt.Sprintf("%s_%s_1", p, "hello")
cn := s.GetContainerByName(c, name)
c.Assert(cn, NotNil)

c.Assert(cn.State.Running, Equals, true)
c.Assert(cn.State.Paused, Equals, false)

s.FromText(c, p, "pause", SimpleTemplate)

cn = s.GetContainerByName(c, name)
c.Assert(cn, NotNil)
c.Assert(cn.State.Running, Equals, true)
c.Assert(cn.State.Paused, Equals, true)

s.FromText(c, p, "pause", SimpleTemplate)

cn = s.GetContainerByName(c, name)
c.Assert(cn, NotNil)
c.Assert(cn.State.Running, Equals, true)
c.Assert(cn.State.Paused, Equals, true)
}

func (s *RunSuite) TestUnpause(c *C) {
p := s.ProjectFromText(c, "up", SimpleTemplate)

name := fmt.Sprintf("%s_%s_1", p, "hello")
cn := s.GetContainerByName(c, name)
c.Assert(cn, NotNil)

c.Assert(cn.State.Running, Equals, true)
c.Assert(cn.State.Paused, Equals, false)

s.FromText(c, p, "pause", SimpleTemplate)

cn = s.GetContainerByName(c, name)
c.Assert(cn, NotNil)
c.Assert(cn.State.Running, Equals, true)
c.Assert(cn.State.Paused, Equals, true)

s.FromText(c, p, "unpause", SimpleTemplate)

cn = s.GetContainerByName(c, name)
c.Assert(cn, NotNil)
c.Assert(cn.State.Running, Equals, true)
c.Assert(cn.State.Paused, Equals, false)
}

func (s *RunSuite) TestUnpauseNotPausedService(c *C) {
p := s.ProjectFromText(c, "up", SimpleTemplate)

name := fmt.Sprintf("%s_%s_1", p, "hello")
cn := s.GetContainerByName(c, name)
c.Assert(cn, NotNil)

c.Assert(cn.State.Running, Equals, true)
c.Assert(cn.State.Paused, Equals, false)

s.FromText(c, p, "unpause", SimpleTemplate)

cn = s.GetContainerByName(c, name)
c.Assert(cn, NotNil)
c.Assert(cn.State.Running, Equals, true)
c.Assert(cn.State.Paused, Equals, false)
}
10 changes: 10 additions & 0 deletions project/empty.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,13 @@ func (e *EmptyService) Scale(count int) error {
func (e *EmptyService) Info(qFlag bool) (InfoSet, error) {
return InfoSet{}, nil
}

// Pause implements Service.Pause but does nothing.
func (e *EmptyService) Pause() error {
return nil
}

// Unpause implements Service.Pause but does nothing.
func (e *EmptyService) Unpause() error {
return nil
}
18 changes: 18 additions & 0 deletions project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,24 @@ func (p *Project) Kill(services ...string) error {
}), nil)
}

// Pause pauses the specified services containers (like docker pause).
func (p *Project) Pause(services ...string) error {
return p.perform(EventProjectPauseStart, EventProjectPauseDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, EventServicePauseStart, EventServicePause, func(service Service) error {
return service.Pause()
})
}), nil)
}

// Unpause pauses the specified services containers (like docker pause).
func (p *Project) Unpause(services ...string) error {
return p.perform(EventProjectUnpauseStart, EventProjectUnpauseDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(nil, EventServiceUnpauseStart, EventServiceUnpause, func(service Service) error {
return service.Unpause()
})
}), nil)
}

func (p *Project) perform(start, done EventType, services []string, action wrapperAction, cycleAction serviceAction) error {
p.Notify(start, "", nil)

Expand Down
10 changes: 10 additions & 0 deletions project/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ const (
EventServiceStart = EventType(iota)
EventServiceBuildStart = EventType(iota)
EventServiceBuild = EventType(iota)
EventServicePauseStart = EventType(iota)
EventServicePause = EventType(iota)
EventServiceUnpauseStart = EventType(iota)
EventServiceUnpause = EventType(iota)

EventProjectDownStart = EventType(iota)
EventProjectDownDone = EventType(iota)
Expand All @@ -51,6 +55,10 @@ const (
EventProjectStartDone = EventType(iota)
EventProjectBuildStart = EventType(iota)
EventProjectBuildDone = EventType(iota)
EventProjectPauseStart = EventType(iota)
EventProjectPauseDone = EventType(iota)
EventProjectUnpauseStart = EventType(iota)
EventProjectUnpauseDone = EventType(iota)
)

func (e EventType) String() string {
Expand Down Expand Up @@ -246,6 +254,8 @@ type Service interface {
DependentServices() []ServiceRelationship
Containers() ([]Container, error)
Scale(count int) error
Pause() error
Unpause() error
}

// Container defines what a libcompose container provides.
Expand Down