No need to filter services again in backend, filter is done by cli command. Added e2e test, labels one-off and slug

Signed-off-by: Guillaume Tardif <guillaume.tardif@gmail.com>
This commit is contained in:
Guillaume Tardif 2020-12-14 17:22:30 +01:00
parent 370781e95e
commit b289138ca9
6 changed files with 116 additions and 131 deletions

View File

@ -44,6 +44,20 @@ func (s *composeService) Create(ctx context.Context, project *types.Project) err
return err
}
if err := s.ensureProjectNetworks(ctx, project); err != nil {
return err
}
if err := s.ensureProjectNetworks(ctx, project); err != nil {
return err
}
return InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
return s.ensureService(c, project, service)
})
}
func (s *composeService) ensureProjectNetworks(ctx context.Context, project *types.Project) error {
for k, network := range project.Networks {
if !network.External.External && network.Name != "" {
network.Name = fmt.Sprintf("%s_%s", project.Name, k)
@ -57,7 +71,10 @@ func (s *composeService) Create(ctx context.Context, project *types.Project) err
return err
}
}
return nil
}
func (s *composeService) ensureProjectVolumes(ctx context.Context, project *types.Project) error {
for k, volume := range project.Volumes {
if !volume.External.External && volume.Name != "" {
volume.Name = fmt.Sprintf("%s_%s", project.Name, k)
@ -71,10 +88,7 @@ func (s *composeService) Create(ctx context.Context, project *types.Project) err
return err
}
}
return InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
return s.ensureService(c, project, service)
})
return nil
}
func getContainerCreateOptions(p *types.Project, s types.ServiceConfig, number int, inherit *moby.Container) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
@ -88,11 +102,15 @@ func getContainerCreateOptions(p *types.Project, s types.ServiceConfig, number i
labels[k] = v
}
// TODO: change oneoffLabel value for containers started with `docker compose run`
labels[projectLabel] = p.Name
labels[serviceLabel] = s.Name
labels[versionLabel] = ComposeVersion
labels[oneoffLabel] = "False"
if _, ok := s.Labels[oneoffLabel]; ok {
labels[oneoffLabel] = s.Labels[oneoffLabel]
labels[slugLabel] = s.Labels[slugLabel]
} else {
labels[oneoffLabel] = "False"
}
labels[configHashLabel] = hash
labels[workingDirLabel] = p.WorkingDir
labels[configFilesLabel] = strings.Join(p.ComposeFiles, ",")

View File

@ -25,6 +25,7 @@ import (
const (
containerNumberLabel = "com.docker.compose.container-number"
oneoffLabel = "com.docker.compose.oneoff"
slugLabel = "com.docker.compose.slug"
projectLabel = "com.docker.compose.project"
volumeLabel = "com.docker.compose.volume"
workingDirLabel = "com.docker.compose.project.working_dir"

View File

@ -21,7 +21,6 @@ import (
"fmt"
"io"
"os"
"sort"
"github.com/compose-spec/compose-go/types"
"github.com/docker/compose-cli/api/compose"
@ -37,12 +36,11 @@ func (s *composeService) CreateOneOffContainer(ctx context.Context, project *typ
return "", err
}
err = s.ensureRequiredNetworks(ctx, project, service)
if err != nil {
if err := s.ensureProjectNetworks(ctx, project); err != nil {
return "", err
}
err = s.ensureRequiredVolumes(ctx, project, service)
if err != nil {
if err := s.ensureProjectVolumes(ctx, project); err != nil {
return "", err
}
// ensure required services are up and running before creating the oneoff container
@ -128,123 +126,27 @@ func updateOneOffServiceConfig(service *types.ServiceConfig, projectName string,
service.Scale = 1
service.ContainerName = fmt.Sprintf("%s_%s_run_%s", projectName, service.Name, moby.TruncateID(slug))
service.Labels = types.Labels{
"com.docker.compose.slug": slug,
"com.docker.compose.oneoff": "True",
slugLabel: slug,
oneoffLabel: "True",
}
service.Tty = true
service.StdinOpen = true
}
func (s *composeService) ensureRequiredServices(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
requiredServices := getDependencyNames(project, service, func() []string {
return service.GetDependencies()
})
if len(requiredServices) > 0 {
// dependencies here
services, err := project.GetServices(requiredServices)
if err != nil {
return err
}
project.Services = services
err = s.ensureImagesExists(ctx, project)
if err != nil {
return err
}
err := s.ensureImagesExists(ctx, project)
if err != nil {
return err
}
err = InDependencyOrder(ctx, project, func(c context.Context, svc types.ServiceConfig) error {
err = InDependencyOrder(ctx, project, func(c context.Context, svc types.ServiceConfig) error {
if svc.Name != service.Name { // only start dependencies, not service to run one-off
return s.ensureService(c, project, svc)
})
if err != nil {
return err
}
return s.Start(ctx, project, nil)
}
return nil
}
func (s *composeService) ensureRequiredNetworks(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
networks := getDependentNetworkNames(project, service)
for k, network := range project.Networks {
if !contains(networks, network.Name) {
continue
}
if !network.External.External && network.Name != "" {
network.Name = fmt.Sprintf("%s_%s", project.Name, k)
project.Networks[k] = network
}
network.Labels = network.Labels.Add(networkLabel, k)
network.Labels = network.Labels.Add(projectLabel, project.Name)
network.Labels = network.Labels.Add(versionLabel, ComposeVersion)
err := s.ensureNetwork(ctx, network)
if err != nil {
return err
}
}
return nil
}
func (s *composeService) ensureRequiredVolumes(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
volumes := getDependentVolumeNames(project, service)
for k, volume := range project.Volumes {
if !contains(volumes, volume.Name) {
continue
}
if !volume.External.External && volume.Name != "" {
volume.Name = fmt.Sprintf("%s_%s", project.Name, k)
project.Volumes[k] = volume
}
volume.Labels = volume.Labels.Add(volumeLabel, k)
volume.Labels = volume.Labels.Add(projectLabel, project.Name)
volume.Labels = volume.Labels.Add(versionLabel, ComposeVersion)
err := s.ensureVolume(ctx, volume)
if err != nil {
return err
}
}
return nil
}
type filterDependency func() []string
func getDependencyNames(project *types.Project, service types.ServiceConfig, f filterDependency) []string {
names := f()
serviceNames := service.GetDependencies()
if len(serviceNames) == 0 {
return names
}
if len(serviceNames) > 0 {
services, _ := project.GetServices(serviceNames)
for _, s := range services {
svc := getDependencyNames(project, s, f)
names = append(names, svc...)
}
}
sort.Strings(names)
return unique(names)
}
func getDependentNetworkNames(project *types.Project, service types.ServiceConfig) []string {
return getDependencyNames(project, service, func() []string {
names := []string{}
for n := range service.Networks {
if contains(project.NetworkNames(), n) {
names = append(names, n)
}
}
return names
})
}
func getDependentVolumeNames(project *types.Project, service types.ServiceConfig) []string {
return getDependencyNames(project, service, func() []string {
names := []string{}
for _, v := range service.Volumes {
if contains(project.VolumeNames(), v.Source) {
names = append(names, v.Source)
}
}
return names
return nil
})
if err != nil {
return err
}
return s.Start(ctx, project, nil)
}

View File

@ -38,14 +38,3 @@ func contains(slice []string, item string) bool {
}
return false
}
func unique(s []string) []string {
items := []string{}
for _, item := range s {
if contains(items, item) {
continue
}
items = append(items, item)
}
return items
}

View File

@ -103,6 +103,57 @@ func TestLocalComposeUp(t *testing.T) {
})
}
func TestLocalComposeRun(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
t.Run("compose run", func(t *testing.T) {
res := c.RunDockerCmd("compose", "run", "-f", "./fixtures/run-test/docker-compose.yml", "back")
res.Assert(t, icmd.Expected{Out: "Hello there!!"})
})
t.Run("check run container exited", func(t *testing.T) {
res := c.RunDockerCmd("ps", "--all")
lines := Lines(res.Stdout())
var runContainerID string
for _, line := range lines {
fields := strings.Fields(line)
containerID := fields[len(fields)-1]
assert.Assert(t, !strings.HasPrefix(containerID, "run-test_front"))
if strings.HasPrefix(containerID, "run-test_back") {
//only the one-off container for back service
assert.Assert(t, strings.HasPrefix(containerID, "run-test_back_run_"), containerID)
runContainerID = containerID
assert.Assert(t, strings.Contains(line, "Exited"), line)
}
if strings.HasPrefix(containerID, "run-test_db_1") {
assert.Assert(t, strings.Contains(line, "Up"), line)
}
}
assert.Assert(t, runContainerID != "")
res = c.RunDockerCmd("inspect", runContainerID)
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.container-number": "1"`})
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.project": "run-test"`})
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.oneoff": "True",`})
res.Assert(t, icmd.Expected{Out: `"com.docker.compose.slug": "`})
})
t.Run("compose run --rm", func(t *testing.T) {
res := c.RunDockerCmd("compose", "run", "-f", "./fixtures/run-test/docker-compose.yml", "--rm", "back")
res.Assert(t, icmd.Expected{Out: "Hello there!!"})
})
t.Run("check run container removed", func(t *testing.T) {
res := c.RunDockerCmd("ps", "--all")
assert.Assert(t, strings.Contains(res.Stdout(), "run-test_back"), res.Stdout())
})
t.Run("down", func(t *testing.T) {
_ = c.RunDockerCmd("compose", "down", "-f", "./fixtures/run-test/docker-compose.yml")
res := c.RunDockerCmd("ps", "--all")
assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout())
})
}
func TestLocalComposeBuild(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)

View File

@ -0,0 +1,24 @@
version: '3.8'
services:
back:
image: alpine
command: echo "Hello there!!"
depends_on:
- db
networks:
- backnet
db:
image: nginx
networks:
- backnet
volumes:
- data:/test
front:
image: nginx
networks:
- frontnet
networks:
frontnet: {}
backnet: {}
volumes:
data: {}