Merge pull request #8635 from ndeloof/project_from_labels

rebuild compose project from labels
This commit is contained in:
Ulysses Souza 2021-09-20 16:24:32 +02:00 committed by GitHub
commit 6b4b2ea633
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 80 additions and 61 deletions

View File

@ -49,6 +49,8 @@ const (
SlugLabel = "com.docker.compose.slug" SlugLabel = "com.docker.compose.slug"
// ImageDigestLabel stores digest of the container image used to run service // ImageDigestLabel stores digest of the container image used to run service
ImageDigestLabel = "com.docker.compose.image" ImageDigestLabel = "com.docker.compose.image"
// DependenciesLabel stores service dependencies
DependenciesLabel = "com.docker.compose.depends_on"
// VersionLabel stores the compose tool version used to run application // VersionLabel stores the compose tool version used to run application
VersionLabel = "com.docker.compose.version" VersionLabel = "com.docker.compose.version"
) )

View File

@ -226,27 +226,11 @@ func getImageName(service types.ServiceConfig, projectName string) string {
func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, service types.ServiceConfig, number int, inherit *moby.Container, func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, service types.ServiceConfig, number int, inherit *moby.Container,
autoRemove bool) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) { autoRemove bool) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
hash, err := ServiceHash(service) labels, err := s.prepareLabels(p, service, number)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
labels := map[string]string{}
for k, v := range service.Labels {
labels[k] = v
}
labels[api.ProjectLabel] = p.Name
labels[api.ServiceLabel] = service.Name
labels[api.VersionLabel] = api.ComposeVersion
if _, ok := service.Labels[api.OneoffLabel]; !ok {
labels[api.OneoffLabel] = "False"
}
labels[api.ConfigHashLabel] = hash
labels[api.WorkingDirLabel] = p.WorkingDir
labels[api.ConfigFilesLabel] = strings.Join(p.ComposeFiles, ",")
labels[api.ContainerNumberLabel] = strconv.Itoa(number)
var ( var (
runCmd strslice.StrSlice runCmd strslice.StrSlice
entrypoint strslice.StrSlice entrypoint strslice.StrSlice
@ -394,6 +378,36 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
return &containerConfig, &hostConfig, networkConfig, nil return &containerConfig, &hostConfig, networkConfig, nil
} }
func (s *composeService) prepareLabels(p *types.Project, service types.ServiceConfig, number int) (map[string]string, error) {
labels := map[string]string{}
for k, v := range service.Labels {
labels[k] = v
}
labels[api.ProjectLabel] = p.Name
labels[api.ServiceLabel] = service.Name
labels[api.VersionLabel] = api.ComposeVersion
if _, ok := service.Labels[api.OneoffLabel]; !ok {
labels[api.OneoffLabel] = "False"
}
hash, err := ServiceHash(service)
if err != nil {
return nil, err
}
labels[api.ConfigHashLabel] = hash
labels[api.WorkingDirLabel] = p.WorkingDir
labels[api.ConfigFilesLabel] = strings.Join(p.ComposeFiles, ",")
labels[api.ContainerNumberLabel] = strconv.Itoa(number)
var dependencies []string
for s := range service.DependsOn {
dependencies = append(dependencies, s)
}
labels[api.DependenciesLabel] = strings.Join(dependencies, ",")
return labels, nil
}
func getDefaultNetworkMode(project *types.Project, service types.ServiceConfig) string { func getDefaultNetworkMode(project *types.Project, service types.ServiceConfig) string {
mode := "none" mode := "none"
if len(project.Networks) > 0 { if len(project.Networks) > 0 {

View File

@ -22,7 +22,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types" moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
@ -52,11 +51,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a
} }
if options.Project == nil { if options.Project == nil {
project, err := s.projectFromContainerLabels(containers, projectName) options.Project = s.projectFromContainerLabels(containers.filter(isNotOneOff), projectName)
if err != nil {
return err
}
options.Project = project
} }
if len(containers) > 0 { if len(containers) > 0 {
@ -242,36 +237,31 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
return eg.Wait() return eg.Wait()
} }
func (s *composeService) projectFromContainerLabels(containers Containers, projectName string) (*types.Project, error) { func (s *composeService) projectFromContainerLabels(containers Containers, projectName string) *types.Project {
fakeProject := &types.Project{ project := &types.Project{
Name: projectName, Name: projectName,
} }
if len(containers) == 0 { if len(containers) == 0 {
return fakeProject, nil return project
} }
options, err := loadProjectOptionsFromLabels(containers[0]) set := map[string]moby.Container{}
if err != nil { for _, c := range containers {
return nil, err set[c.Labels[api.ServiceLabel]] = c
} }
if options.ConfigPaths[0] == "-" { for s, c := range set {
for _, container := range containers { service := types.ServiceConfig{
fakeProject.Services = append(fakeProject.Services, types.ServiceConfig{ Name: s,
Name: container.Labels[api.ServiceLabel], Image: c.Image,
}) Labels: c.Labels,
} }
return fakeProject, nil dependencies := c.Labels[api.DependenciesLabel]
if len(dependencies) > 0 {
service.DependsOn = types.DependsOnConfig{}
for _, d := range strings.Split(dependencies, ",") {
service.DependsOn[d] = types.ServiceDependency{}
}
}
project.Services = append(project.Services, service)
} }
project, err := cli.ProjectFromOptions(options) return project
if err != nil {
return nil, err
}
return project, nil
}
func loadProjectOptionsFromLabels(c moby.Container) (*cli.ProjectOptions, error) {
return cli.NewProjectOptions(strings.Split(c.Labels[api.ConfigFilesLabel], ","),
cli.WithOsEnv,
cli.WithWorkingDirectory(c.Labels[api.WorkingDirLabel]),
cli.WithName(c.Labels[api.ProjectLabel]))
} }

View File

@ -38,7 +38,12 @@ func TestDown(t *testing.T) {
tested.apiClient = api tested.apiClient = api
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return( api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
[]moby.Container{testContainer("service1", "123"), testContainer("service2", "456"), testContainer("service2", "789"), testContainer("service_orphan", "321")}, nil) []moby.Container{
testContainer("service1", "123", false),
testContainer("service2", "456", false),
testContainer("service2", "789", false),
testContainer("service_orphan", "321", true),
}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil) api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "456", nil).Return(nil) api.EXPECT().ContainerStop(gomock.Any(), "456", nil).Return(nil)
@ -64,7 +69,11 @@ func TestDownRemoveOrphans(t *testing.T) {
tested.apiClient = api tested.apiClient = api
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return( api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
[]moby.Container{testContainer("service1", "123"), testContainer("service2", "789"), testContainer("service_orphan", "321")}, nil) []moby.Container{
testContainer("service1", "123", false),
testContainer("service2", "789", false),
testContainer("service_orphan", "321", true),
}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil) api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil) api.EXPECT().ContainerStop(gomock.Any(), "789", nil).Return(nil)
@ -90,7 +99,7 @@ func TestDownRemoveVolumes(t *testing.T) {
tested.apiClient = api tested.apiClient = api
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return( api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
[]moby.Container{testContainer("service1", "123")}, nil) []moby.Container{testContainer("service1", "123", false)}, nil)
api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil) api.EXPECT().ContainerStop(gomock.Any(), "123", nil).Return(nil)
api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true, RemoveVolumes: true}).Return(nil) api.EXPECT().ContainerRemove(gomock.Any(), "123", moby.ContainerRemoveOptions{Force: true, RemoveVolumes: true}).Return(nil)

View File

@ -46,7 +46,7 @@ func TestKillAll(t *testing.T) {
ctx := context.Background() ctx := context.Background()
api.EXPECT().ContainerList(ctx, projectFilterListOpt()).Return( api.EXPECT().ContainerList(ctx, projectFilterListOpt()).Return(
[]moby.Container{testContainer("service1", "123"), testContainer("service1", "456"), testContainer("service2", "789")}, nil) []moby.Container{testContainer("service1", "123", false), testContainer("service1", "456", false), testContainer("service2", "789", false)}, nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "123", "").Return(nil) api.EXPECT().ContainerKill(anyCancellableContext(), "123", "").Return(nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "456", "").Return(nil) api.EXPECT().ContainerKill(anyCancellableContext(), "456", "").Return(nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "789", "").Return(nil) api.EXPECT().ContainerKill(anyCancellableContext(), "789", "").Return(nil)
@ -64,7 +64,7 @@ func TestKillSignal(t *testing.T) {
project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{testService("service1")}} project := types.Project{Name: strings.ToLower(testProject), Services: []types.ServiceConfig{testService("service1")}}
ctx := context.Background() ctx := context.Background()
api.EXPECT().ContainerList(ctx, projectFilterListOpt()).Return([]moby.Container{testContainer("service1", "123")}, nil) api.EXPECT().ContainerList(ctx, projectFilterListOpt()).Return([]moby.Container{testContainer("service1", "123", false)}, nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "123", "SIGTERM").Return(nil) api.EXPECT().ContainerKill(anyCancellableContext(), "123", "SIGTERM").Return(nil)
err := tested.kill(ctx, &project, compose.KillOptions{Signal: "SIGTERM"}) err := tested.kill(ctx, &project, compose.KillOptions{Signal: "SIGTERM"})
@ -75,22 +75,26 @@ func testService(name string) types.ServiceConfig {
return types.ServiceConfig{Name: name} return types.ServiceConfig{Name: name}
} }
func testContainer(service string, id string) moby.Container { func testContainer(service string, id string, oneOff bool) moby.Container {
return moby.Container{ return moby.Container{
ID: id, ID: id,
Names: []string{id}, Names: []string{id},
Labels: containerLabels(service), Labels: containerLabels(service, oneOff),
} }
} }
func containerLabels(service string) map[string]string { func containerLabels(service string, oneOff bool) map[string]string {
workingdir, _ := filepath.Abs("testdata") workingdir, _ := filepath.Abs("testdata")
composefile := filepath.Join(workingdir, "compose.yaml") composefile := filepath.Join(workingdir, "compose.yaml")
return map[string]string{ labels := map[string]string{
compose.ServiceLabel: service, compose.ServiceLabel: service,
compose.ConfigFilesLabel: composefile, compose.ConfigFilesLabel: composefile,
compose.WorkingDirLabel: workingdir, compose.WorkingDirLabel: workingdir,
compose.ProjectLabel: strings.ToLower(testProject)} compose.ProjectLabel: strings.ToLower(testProject)}
if oneOff {
labels[compose.OneoffLabel] = "True"
}
return labels
} }
func anyCancellableContext() gomock.Matcher { func anyCancellableContext() gomock.Matcher {

View File

@ -66,7 +66,7 @@ func containerDetails(service string, id string, status string, health string, e
container := moby.Container{ container := moby.Container{
ID: id, ID: id,
Names: []string{"/" + id}, Names: []string{"/" + id},
Labels: containerLabels(service), Labels: containerLabels(service, false),
State: status, State: status,
} }
inspect := moby.ContainerJSON{ContainerJSONBase: &moby.ContainerJSONBase{State: &moby.ContainerState{Status: status, Health: &moby.Health{Status: health}, ExitCode: exitCode}}} inspect := moby.ContainerJSON{ContainerJSONBase: &moby.ContainerJSONBase{State: &moby.ContainerState{Status: status, Health: &moby.Health{Status: health}, ExitCode: exitCode}}}

View File

@ -40,9 +40,9 @@ func TestStopTimeout(t *testing.T) {
ctx := context.Background() ctx := context.Background()
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return( api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
[]moby.Container{ []moby.Container{
testContainer("service1", "123"), testContainer("service1", "123", false),
testContainer("service1", "456"), testContainer("service1", "456", false),
testContainer("service2", "789"), testContainer("service2", "789", false),
}, nil) }, nil)
timeout := time.Duration(2) * time.Second timeout := time.Duration(2) * time.Second