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"
// ImageDigestLabel stores digest of the container image used to run service
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 = "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,
autoRemove bool) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
hash, err := ServiceHash(service)
labels, err := s.prepareLabels(p, service, number)
if err != nil {
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 (
runCmd strslice.StrSlice
entrypoint strslice.StrSlice
@ -394,6 +378,36 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project,
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 {
mode := "none"
if len(project.Networks) > 0 {

View File

@ -22,7 +22,6 @@ import (
"strings"
"time"
"github.com/compose-spec/compose-go/cli"
"github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types"
"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 {
project, err := s.projectFromContainerLabels(containers, projectName)
if err != nil {
return err
}
options.Project = project
options.Project = s.projectFromContainerLabels(containers.filter(isNotOneOff), projectName)
}
if len(containers) > 0 {
@ -242,36 +237,31 @@ func (s *composeService) removeContainers(ctx context.Context, w progress.Writer
return eg.Wait()
}
func (s *composeService) projectFromContainerLabels(containers Containers, projectName string) (*types.Project, error) {
fakeProject := &types.Project{
func (s *composeService) projectFromContainerLabels(containers Containers, projectName string) *types.Project {
project := &types.Project{
Name: projectName,
}
if len(containers) == 0 {
return fakeProject, nil
return project
}
options, err := loadProjectOptionsFromLabels(containers[0])
if err != nil {
return nil, err
set := map[string]moby.Container{}
for _, c := range containers {
set[c.Labels[api.ServiceLabel]] = c
}
if options.ConfigPaths[0] == "-" {
for _, container := range containers {
fakeProject.Services = append(fakeProject.Services, types.ServiceConfig{
Name: container.Labels[api.ServiceLabel],
})
for s, c := range set {
service := types.ServiceConfig{
Name: s,
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)
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]))
return project
}

View File

@ -38,7 +38,12 @@ func TestDown(t *testing.T) {
tested.apiClient = api
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(), "456", nil).Return(nil)
@ -64,7 +69,11 @@ func TestDownRemoveOrphans(t *testing.T) {
tested.apiClient = api
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(), "789", nil).Return(nil)
@ -90,7 +99,7 @@ func TestDownRemoveVolumes(t *testing.T) {
tested.apiClient = api
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().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()
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(), "456", "").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")}}
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)
err := tested.kill(ctx, &project, compose.KillOptions{Signal: "SIGTERM"})
@ -75,22 +75,26 @@ func testService(name string) types.ServiceConfig {
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{
ID: 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")
composefile := filepath.Join(workingdir, "compose.yaml")
return map[string]string{
labels := map[string]string{
compose.ServiceLabel: service,
compose.ConfigFilesLabel: composefile,
compose.WorkingDirLabel: workingdir,
compose.ProjectLabel: strings.ToLower(testProject)}
if oneOff {
labels[compose.OneoffLabel] = "True"
}
return labels
}
func anyCancellableContext() gomock.Matcher {

View File

@ -66,7 +66,7 @@ func containerDetails(service string, id string, status string, health string, e
container := moby.Container{
ID: id,
Names: []string{"/" + id},
Labels: containerLabels(service),
Labels: containerLabels(service, false),
State: status,
}
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()
api.EXPECT().ContainerList(gomock.Any(), projectFilterListOpt()).Return(
[]moby.Container{
testContainer("service1", "123"),
testContainer("service1", "456"),
testContainer("service2", "789"),
testContainer("service1", "123", false),
testContainer("service1", "456", false),
testContainer("service2", "789", false),
}, nil)
timeout := time.Duration(2) * time.Second