Skip to content

Commit

Permalink
introduce composeService.ActualState and cleanup
Browse files Browse the repository at this point in the history
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
  • Loading branch information
ndeloof committed Mar 9, 2022
1 parent 1ce67e8 commit 0032d95
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 84 deletions.
98 changes: 69 additions & 29 deletions pkg/compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"strings"

"github.com/docker/compose/v2/pkg/api"
"github.com/docker/docker/api/types/filters"
"github.com/pkg/errors"

"github.com/compose-spec/compose-go/types"
Expand Down Expand Up @@ -94,13 +95,50 @@ func escapeDollarSign(marshal []byte) []byte {
return bytes.ReplaceAll(marshal, dollar, escDollar)
}

// projectFromName builds a types.Project based on actual resources with compose labels set
func (s *composeService) projectFromName(containers Containers, projectName string, services ...string) (*types.Project, error) {
// ActualState builds a types.Project based on actual resources with compose labels set
func (s *composeService) ActualState(ctx context.Context, projectName string, services []string) (Containers, *types.Project, error) {
containers, project, err := s.actualServices(ctx, projectName)
if err != nil && !api.IsNotFoundError(err) {
return nil, nil, err
}

volumes, err := s.actualVolumes(ctx, projectName)
if err != nil {
return nil, nil, err
}
project.Volumes = volumes

networks, err := s.actualNetworks(ctx, projectName)
if err != nil {
return nil, nil, err
}
project.Networks = networks

err = project.ForServices(services)
if err != nil {
return nil, project, err
}

if len(services) > 0 {
containers = containers.filter(isService(services...))
}
return containers, project, nil
}

func (s *composeService) actualServices(ctx context.Context, projectName string) (Containers, *types.Project, error) {
project := &types.Project{
Name: projectName,
}

var containers Containers
// don't filter containers by options.Services so actualServices can rebuild project with all existing resources
containers, err := s.getContainers(ctx, projectName, oneOffInclude, true)
if err != nil {
return nil, nil, err
}

if len(containers) == 0 {
return project, errors.Wrap(api.ErrNotFound, fmt.Sprintf("no container found for project %q", projectName))
return nil, project, errors.Wrap(api.ErrNotFound, fmt.Sprintf("no container found for project %q", projectName))
}
set := map[string]*types.ServiceConfig{}
for _, c := range containers {
Expand Down Expand Up @@ -133,39 +171,41 @@ func (s *composeService) projectFromName(containers Containers, projectName stri
}
project.Services = append(project.Services, *service)
}
SERVICES:
for _, qs := range services {
for _, es := range project.Services {
if es.Name == qs {
continue SERVICES
}
}
return project, errors.New("no such service: " + qs)
}
err := project.ForServices(services)
return containers, project, nil
}

func (s *composeService) actualNetworks(ctx context.Context, projectName string) (types.Networks, error) {
list, err := s.apiClient.NetworkList(ctx, moby.NetworkListOptions{
Filters: filters.NewArgs(projectFilter(projectName)),
})
if err != nil {
return project, err
return nil, err
}

return project, nil
networks := types.Networks{}
for _, net := range list {
networks[net.Labels[api.VolumeLabel]] = types.NetworkConfig{
Name: net.Name,
Driver: net.Driver,
Labels: net.Labels,
}
}
return networks, nil
}

// actualState list resources labelled by projectName to rebuild compose project model
func (s *composeService) actualState(ctx context.Context, projectName string, services []string) (Containers, *types.Project, error) {
var containers Containers
// don't filter containers by options.Services so projectFromName can rebuild project with all existing resources
containers, err := s.getContainers(ctx, projectName, oneOffInclude, true)
func (s *composeService) actualVolumes(ctx context.Context, projectName string) (types.Volumes, error) {
list, err := s.apiClient.VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
if err != nil {
return nil, nil, err
return nil, err
}

project, err := s.projectFromName(containers, projectName, services...)
if err != nil && !api.IsNotFoundError(err) {
return nil, nil, err
}

if len(services) > 0 {
containers = containers.filter(isService(services...))
volumes := types.Volumes{}
for _, vol := range list.Volumes {
volumes[vol.Labels[api.VolumeLabel]] = types.VolumeConfig{
Name: vol.Name,
Driver: vol.Driver,
Labels: vol.Labels,
}
}
return containers, project, nil
return volumes, nil
}
61 changes: 23 additions & 38 deletions pkg/compose/down.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,21 @@ func (s *composeService) Down(ctx context.Context, projectName string, options a
}

func (s *composeService) down(ctx context.Context, projectName string, options api.DownOptions) error {
builtFromResources := options.Project == nil
w := progress.ContextWriter(ctx)
resourceToRemove := false

var containers Containers
containers, err := s.getContainers(ctx, projectName, oneOffInclude, true)
if err != nil {
return err
}

if builtFromResources {
options.Project, err = s.getProjectWithVolumes(ctx, containers, projectName)
var (
containers Containers
project = options.Project
err error
)
if project == nil {
containers, project, err = s.ActualState(ctx, projectName, nil)
if err != nil {
return err
}
} else {
containers, err = s.getContainers(ctx, projectName, oneOffInclude, true)
if err != nil {
return err
}
Expand All @@ -62,7 +65,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
resourceToRemove = true
}

err = InReverseDependencyOrder(ctx, options.Project, func(c context.Context, service string) error {
err = InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
serviceContainers := containers.filter(isService(service))
err := s.removeContainers(ctx, w, serviceContainers, options.Timeout, options.Volumes)
return err
Expand All @@ -71,7 +74,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
return err
}

orphans := containers.filter(isNotService(options.Project.ServiceNames()...))
orphans := containers.filter(isNotService(project.ServiceNames()...))
if options.RemoveOrphans && len(orphans) > 0 {
err := s.removeContainers(ctx, w, orphans, options.Timeout, false)
if err != nil {
Expand All @@ -85,11 +88,12 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
}

if options.Images != "" {
ops = append(ops, s.ensureImagesDown(ctx, projectName, options, w)...)
local := options.Images == "local"
ops = append(ops, s.ensureImagesDown(ctx, project, local, w)...)
}

if options.Volumes {
ops = append(ops, s.ensureVolumesDown(ctx, options.Project, w)...)
ops = append(ops, s.ensureVolumesDown(ctx, project, w)...)
}

if !resourceToRemove && len(ops) == 0 {
Expand All @@ -114,9 +118,9 @@ func (s *composeService) ensureVolumesDown(ctx context.Context, project *types.P
return ops
}

func (s *composeService) ensureImagesDown(ctx context.Context, projectName string, options api.DownOptions, w progress.Writer) []downOp {
func (s *composeService) ensureImagesDown(ctx context.Context, project *types.Project, local bool, w progress.Writer) []downOp {
var ops []downOp
for image := range s.getServiceImages(options, projectName) {
for image := range s.getServiceImages(local, project) {
image := image
ops = append(ops, func() error {
return s.removeImage(ctx, image, w)
Expand All @@ -141,15 +145,15 @@ func (s *composeService) ensureNetworksDown(ctx context.Context, projectName str
return ops, nil
}

func (s *composeService) getServiceImages(options api.DownOptions, projectName string) map[string]struct{} {
func (s *composeService) getServiceImages(local bool, project *types.Project) map[string]struct{} {
images := map[string]struct{}{}
for _, service := range options.Project.Services {
for _, service := range project.Services {
image := service.Image
if options.Images == "local" && image != "" {
if local && image != "" {
continue
}
if image == "" {
image = getImageName(service, projectName)
image = getImageName(service, project.Name)
}
images[image] = struct{}{}
}
Expand Down Expand Up @@ -232,22 +236,3 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
}
return eg.Wait()
}

func (s *composeService) getProjectWithVolumes(ctx context.Context, containers Containers, projectName string) (*types.Project, error) {
containers = containers.filter(isNotOneOff)
project, _ := s.projectFromName(containers, projectName)
volumes, err := s.apiClient.VolumeList(ctx, filters.NewArgs(projectFilter(projectName)))
if err != nil {
return nil, err
}

project.Volumes = types.Volumes{}
for _, vol := range volumes.Volumes {
project.Volumes[vol.Labels[api.VolumeLabel]] = types.VolumeConfig{
Name: vol.Name,
Driver: vol.Driver,
Labels: vol.Labels,
}
}
return project, nil
}
2 changes: 1 addition & 1 deletion pkg/compose/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
)

func (s *composeService) Remove(ctx context.Context, projectName string, options api.RemoveOptions) error {
containers, _, err := s.actualState(ctx, projectName, options.Services)
containers, _, err := s.ActualState(ctx, projectName, options.Services)
if err != nil {
return err
}
Expand Down
10 changes: 2 additions & 8 deletions pkg/compose/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,7 @@ func (s *composeService) Restart(ctx context.Context, projectName string, option
}

func (s *composeService) restart(ctx context.Context, projectName string, options api.RestartOptions) error {

observedState, err := s.getContainers(ctx, projectName, oneOffInclude, true)
if err != nil {
return err
}

project, err := s.projectFromName(observedState, projectName, options.Services...)
containers, project, err := s.ActualState(ctx, projectName, options.Services)
if err != nil {
return err
}
Expand All @@ -54,7 +48,7 @@ func (s *composeService) restart(ctx context.Context, projectName string, option
return nil
}
eg, ctx := errgroup.WithContext(ctx)
for _, container := range observedState.filter(isService(service)) {
for _, container := range containers.filter(isService(service)) {
container := container
eg.Go(func() error {
eventName := getContainerProgressName(container)
Expand Down
8 changes: 1 addition & 7 deletions pkg/compose/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,7 @@ func (s *composeService) Start(ctx context.Context, projectName string, options
}

func (s *composeService) start(ctx context.Context, projectName string, options api.StartOptions, listener api.ContainerEventListener) error {
var containers Containers
containers, err := s.getContainers(ctx, projectName, oneOffExclude, true)
if err != nil {
return err
}

project, err := s.projectFromName(containers, projectName, options.AttachTo...)
_, project, err := s.ActualState(ctx, projectName, options.AttachTo)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/compose/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (s *composeService) Stop(ctx context.Context, projectName string, options a
func (s *composeService) stop(ctx context.Context, projectName string, options api.StopOptions) error {
w := progress.ContextWriter(ctx)

containers, project, err := s.actualState(ctx, projectName, options.Services)
containers, project, err := s.ActualState(ctx, projectName, options.Services)
if err != nil {
return err
}
Expand Down

0 comments on commit 0032d95

Please sign in to comment.