mirror of https://github.com/docker/compose.git
Merge pull request #8635 from ndeloof/project_from_labels
rebuild compose project from labels
This commit is contained in:
commit
6b4b2ea633
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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, err := cli.ProjectFromOptions(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return project, nil
|
||||
project.Services = append(project.Services, service)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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}}}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue