Split compose.go into command-focussed go files

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2020-12-08 11:53:36 +01:00
parent 5d893fc098
commit 1eb40999e2
No known key found for this signature in database
GPG Key ID: 9858809D6F8F6E7E
24 changed files with 1501 additions and 1209 deletions

View File

@ -28,12 +28,13 @@ import (
"github.com/docker/compose-cli/api/volumes"
"github.com/docker/compose-cli/backend"
"github.com/docker/compose-cli/context/cloud"
local_compose "github.com/docker/compose-cli/local/compose"
)
type local struct {
*containerService
*volumeService
*composeService
containerService *containerService
volumeService *volumeService
composeService compose.Service
}
func init() {
@ -49,7 +50,7 @@ func service(ctx context.Context) (backend.Service, error) {
return &local{
containerService: &containerService{apiClient},
volumeService: &volumeService{apiClient},
composeService: &composeService{apiClient},
composeService: local_compose.NewComposeService(apiClient),
}, nil
}

File diff suppressed because it is too large Load Diff

129
local/compose/attach.go Normal file
View File

@ -0,0 +1,129 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"context"
"fmt"
"io"
"strings"
"github.com/docker/compose-cli/api/compose"
convert "github.com/docker/compose-cli/local/moby"
"github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/pkg/stdcopy"
"golang.org/x/sync/errgroup"
)
func (s *composeService) attach(ctx context.Context, project *types.Project, consumer compose.LogConsumer) (*errgroup.Group, error) {
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(
projectFilter(project.Name),
),
All: true,
})
if err != nil {
return nil, err
}
var names []string
for _, c := range containers {
names = append(names, getContainerName(c))
}
fmt.Printf("Attaching to %s\n", strings.Join(names, ", "))
eg, ctx := errgroup.WithContext(ctx)
for _, c := range containers {
container := c
eg.Go(func() error {
return s.attachContainer(ctx, container, consumer, project)
})
}
return eg, nil
}
func (s *composeService) attachContainer(ctx context.Context, container moby.Container, consumer compose.LogConsumer, project *types.Project) error {
serviceName := container.Labels[serviceLabel]
w := getWriter(serviceName, container.ID, consumer)
service, err := project.GetService(serviceName)
if err != nil {
return err
}
return s.attachContainerStreams(ctx, container, service.Tty, nil, w)
}
func (s *composeService) attachContainerStreams(ctx context.Context, container moby.Container, tty bool, r io.Reader, w io.Writer) error {
stdin, stdout, err := s.getContainerStreams(ctx, container)
if err != nil {
return err
}
go func() {
<-ctx.Done()
stdout.Close() //nolint:errcheck
stdin.Close() //nolint:errcheck
}()
if r != nil && stdin != nil {
go func() {
io.Copy(stdin, r) //nolint:errcheck
}()
}
if w != nil {
if tty {
_, err = io.Copy(w, stdout)
} else {
_, err = stdcopy.StdCopy(w, w, stdout)
}
}
return err
}
func (s *composeService) getContainerStreams(ctx context.Context, container moby.Container) (io.WriteCloser, io.ReadCloser, error) {
var stdout io.ReadCloser
var stdin io.WriteCloser
if container.State == convert.ContainerRunning {
logs, err := s.apiClient.ContainerLogs(ctx, container.ID, moby.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: true,
})
if err != nil {
return nil, nil, err
}
stdout = logs
} else {
cnx, err := s.apiClient.ContainerAttach(ctx, container.ID, moby.ContainerAttachOptions{
Stream: true,
Stdin: true,
Stdout: true,
Stderr: true,
})
if err != nil {
return nil, nil, err
}
stdout = convert.ContainerStdout{HijackedResponse: cnx}
stdin = convert.ContainerStdin{HijackedResponse: cnx}
}
return stdin, stdout, nil
}

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
package local
package compose
import (
"context"
@ -23,15 +23,29 @@ import (
"path"
"strings"
"github.com/docker/docker/errdefs"
"github.com/compose-spec/compose-go/types"
"github.com/docker/buildx/build"
"github.com/docker/buildx/driver"
_ "github.com/docker/buildx/driver/docker" // required to get default driver registered
"github.com/docker/buildx/util/progress"
"github.com/docker/docker/errdefs"
)
func (s *composeService) Build(ctx context.Context, project *types.Project) error {
opts := map[string]build.Options{}
for _, service := range project.Services {
if service.Build != nil {
imageName := service.Image
if imageName == "" {
imageName = project.Name + "_" + service.Name
}
opts[imageName] = s.toBuildOptions(service, project.WorkingDir, imageName)
}
}
return s.build(ctx, project, opts)
}
func (s *composeService) ensureImagesExists(ctx context.Context, project *types.Project) error {
opts := map[string]build.Options{}
for _, service := range project.Services {

66
local/compose/compose.go Normal file
View File

@ -0,0 +1,66 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/docker/compose-cli/api/compose"
"github.com/compose-spec/compose-go/types"
errdefs2 "github.com/docker/compose-cli/errdefs"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/sanathkr/go-yaml"
)
// NewComposeService create a local implementation of the compose.Service API
func NewComposeService(apiClient *client.Client) compose.Service {
return &composeService{apiClient: apiClient}
}
type composeService struct {
apiClient *client.Client
}
func (s *composeService) Up(ctx context.Context, project *types.Project, detach bool) error {
return errdefs2.ErrNotImplemented
}
func getContainerName(c moby.Container) string {
// Names return container canonical name /foo + link aliases /linked_by/foo
for _, name := range c.Names {
if strings.LastIndex(name, "/") == 0 {
return name[1:]
}
}
return c.Names[0][1:]
}
func (s *composeService) Convert(ctx context.Context, project *types.Project, format string) ([]byte, error) {
switch format {
case "json":
return json.MarshalIndent(project, "", " ")
case "yaml":
return yaml.Marshal(project)
default:
return nil, fmt.Errorf("unsupported format %q", format)
}
}

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
package local
package compose
import (
"os"
@ -113,7 +113,7 @@ func TestStacksMixedStatus(t *testing.T) {
func TestBuildBindMount(t *testing.T) {
volume := composetypes.ServiceVolumeConfig{
Type: composetypes.VolumeTypeBind,
Source: "compose/e2e/volume-test",
Source: "e2e/volume-test",
Target: "/data",
}
mount, err := buildMount(volume)

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
package local
package compose
import (
"context"
@ -28,6 +28,7 @@ import (
"github.com/docker/docker/api/types/network"
"golang.org/x/sync/errgroup"
status "github.com/docker/compose-cli/local/moby"
"github.com/docker/compose-cli/progress"
)
@ -99,10 +100,10 @@ func (s *composeService) ensureService(ctx context.Context, project *types.Proje
w := progress.ContextWriter(ctx)
switch container.State {
case containerRunning:
case status.ContainerRunning:
w.Event(progress.RunningEvent(name))
case containerCreated:
case containerRestarting:
case status.ContainerCreated:
case status.ContainerRestarting:
w.Event(progress.CreatedEvent(name))
default:
eg.Go(func() error {
@ -304,7 +305,7 @@ func (s *composeService) startService(ctx context.Context, project *types.Projec
eg, ctx := errgroup.WithContext(ctx)
for _, c := range containers {
container := c
if container.State == containerRunning {
if container.State == status.ContainerRunning {
continue
}
eg.Go(func() error {

411
local/compose/create.go Normal file
View File

@ -0,0 +1,411 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"context"
"fmt"
"path/filepath"
"strconv"
"strings"
convert "github.com/docker/compose-cli/local/moby"
"github.com/docker/compose-cli/progress"
"github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/strslice"
volume_api "github.com/docker/docker/api/types/volume"
"github.com/docker/docker/errdefs"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
)
func (s *composeService) Create(ctx context.Context, project *types.Project) error {
err := s.ensureImagesExists(ctx, project)
if err != nil {
return err
}
for k, network := range project.Networks {
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
}
}
for k, volume := range project.Volumes {
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 InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
return s.ensureService(c, project, service)
})
}
func getContainerCreateOptions(p *types.Project, s types.ServiceConfig, number int, inherit *moby.Container) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
hash, err := jsonHash(s)
if err != nil {
return nil, nil, nil, err
}
// TODO: change oneoffLabel value for containers started with `docker compose run`
labels := map[string]string{
projectLabel: p.Name,
serviceLabel: s.Name,
versionLabel: ComposeVersion,
oneoffLabel: "False",
configHashLabel: hash,
workingDirLabel: p.WorkingDir,
configFilesLabel: strings.Join(p.ComposeFiles, ","),
containerNumberLabel: strconv.Itoa(number),
}
var (
runCmd strslice.StrSlice
entrypoint strslice.StrSlice
)
if len(s.Command) > 0 {
runCmd = strslice.StrSlice(s.Command)
}
if len(s.Entrypoint) > 0 {
entrypoint = strslice.StrSlice(s.Entrypoint)
}
image := s.Image
if s.Image == "" {
image = fmt.Sprintf("%s_%s", p.Name, s.Name)
}
var (
tty = s.Tty
stdinOpen = s.StdinOpen
attachStdin = false
)
containerConfig := container.Config{
Hostname: s.Hostname,
Domainname: s.DomainName,
User: s.User,
ExposedPorts: buildContainerPorts(s),
Tty: tty,
OpenStdin: stdinOpen,
StdinOnce: true,
AttachStdin: attachStdin,
AttachStderr: true,
AttachStdout: true,
Cmd: runCmd,
Image: image,
WorkingDir: s.WorkingDir,
Entrypoint: entrypoint,
NetworkDisabled: s.NetworkMode == "disabled",
MacAddress: s.MacAddress,
Labels: labels,
StopSignal: s.StopSignal,
Env: convert.ToMobyEnv(s.Environment),
Healthcheck: convert.ToMobyHealthCheck(s.HealthCheck),
// Volumes: // FIXME unclear to me the overlap with HostConfig.Mounts
StopTimeout: convert.ToSeconds(s.StopGracePeriod),
}
mountOptions, err := buildContainerMountOptions(s, inherit)
if err != nil {
return nil, nil, nil, err
}
bindings := buildContainerBindingOptions(s)
networkMode := getNetworkMode(p, s)
hostConfig := container.HostConfig{
Mounts: mountOptions,
CapAdd: strslice.StrSlice(s.CapAdd),
CapDrop: strslice.StrSlice(s.CapDrop),
NetworkMode: networkMode,
Init: s.Init,
ReadonlyRootfs: s.ReadOnly,
// ShmSize: , TODO
Sysctls: s.Sysctls,
PortBindings: bindings,
}
networkConfig := buildDefaultNetworkConfig(s, networkMode)
return &containerConfig, &hostConfig, networkConfig, nil
}
func buildContainerPorts(s types.ServiceConfig) nat.PortSet {
ports := nat.PortSet{}
for _, p := range s.Ports {
p := nat.Port(fmt.Sprintf("%d/%s", p.Target, p.Protocol))
ports[p] = struct{}{}
}
return ports
}
func buildContainerBindingOptions(s types.ServiceConfig) nat.PortMap {
bindings := nat.PortMap{}
for _, port := range s.Ports {
p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
bind := []nat.PortBinding{}
binding := nat.PortBinding{}
if port.Published > 0 {
binding.HostPort = fmt.Sprint(port.Published)
}
bind = append(bind, binding)
bindings[p] = bind
}
return bindings
}
func buildContainerMountOptions(s types.ServiceConfig, inherit *moby.Container) ([]mount.Mount, error) {
mounts := []mount.Mount{}
var inherited []string
if inherit != nil {
for _, m := range inherit.Mounts {
if m.Type == "tmpfs" {
continue
}
src := m.Source
if m.Type == "volume" {
src = m.Name
}
mounts = append(mounts, mount.Mount{
Type: m.Type,
Source: src,
Target: m.Destination,
ReadOnly: !m.RW,
})
inherited = append(inherited, m.Destination)
}
}
for _, v := range s.Volumes {
if contains(inherited, v.Target) {
continue
}
mount, err := buildMount(v)
if err != nil {
return nil, err
}
mounts = append(mounts, mount)
}
return mounts, nil
}
func buildMount(volume types.ServiceVolumeConfig) (mount.Mount, error) {
source := volume.Source
if volume.Type == "bind" && !filepath.IsAbs(source) {
// volume source has already been prefixed with workdir if required, by compose-go project loader
var err error
source, err = filepath.Abs(source)
if err != nil {
return mount.Mount{}, err
}
}
return mount.Mount{
Type: mount.Type(volume.Type),
Source: source,
Target: volume.Target,
ReadOnly: volume.ReadOnly,
Consistency: mount.Consistency(volume.Consistency),
BindOptions: buildBindOption(volume.Bind),
VolumeOptions: buildVolumeOptions(volume.Volume),
TmpfsOptions: buildTmpfsOptions(volume.Tmpfs),
}, nil
}
func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
if bind == nil {
return nil
}
return &mount.BindOptions{
Propagation: mount.Propagation(bind.Propagation),
// NonRecursive: false, FIXME missing from model ?
}
}
func buildVolumeOptions(vol *types.ServiceVolumeVolume) *mount.VolumeOptions {
if vol == nil {
return nil
}
return &mount.VolumeOptions{
NoCopy: vol.NoCopy,
// Labels: , // FIXME missing from model ?
// DriverConfig: , // FIXME missing from model ?
}
}
func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
if tmpfs == nil {
return nil
}
return &mount.TmpfsOptions{
SizeBytes: tmpfs.Size,
// Mode: , // FIXME missing from model ?
}
}
func buildDefaultNetworkConfig(s types.ServiceConfig, networkMode container.NetworkMode) *network.NetworkingConfig {
config := map[string]*network.EndpointSettings{}
net := string(networkMode)
config[net] = &network.EndpointSettings{
Aliases: getAliases(s, s.Networks[net]),
}
return &network.NetworkingConfig{
EndpointsConfig: config,
}
}
func getAliases(s types.ServiceConfig, c *types.ServiceNetworkConfig) []string {
aliases := []string{s.Name}
if c != nil {
aliases = append(aliases, c.Aliases...)
}
return aliases
}
func getNetworkMode(p *types.Project, service types.ServiceConfig) container.NetworkMode {
mode := service.NetworkMode
if mode == "" {
if len(p.Networks) > 0 {
for name := range getNetworksForService(service) {
return container.NetworkMode(p.Networks[name].Name)
}
}
return container.NetworkMode("none")
}
// FIXME incomplete implementation
if strings.HasPrefix(mode, "service:") {
panic("Not yet implemented")
}
if strings.HasPrefix(mode, "container:") {
panic("Not yet implemented")
}
return container.NetworkMode(mode)
}
func getNetworksForService(s types.ServiceConfig) map[string]*types.ServiceNetworkConfig {
if len(s.Networks) > 0 {
return s.Networks
}
return map[string]*types.ServiceNetworkConfig{"default": nil}
}
func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfig) error {
_, err := s.apiClient.NetworkInspect(ctx, n.Name, moby.NetworkInspectOptions{})
if err != nil {
if errdefs.IsNotFound(err) {
if n.External.External {
return fmt.Errorf("network %s declared as external, but could not be found", n.Name)
}
createOpts := moby.NetworkCreate{
// TODO NameSpace Labels
Labels: n.Labels,
Driver: n.Driver,
Options: n.DriverOpts,
Internal: n.Internal,
Attachable: n.Attachable,
}
if n.Ipam.Driver != "" || len(n.Ipam.Config) > 0 {
createOpts.IPAM = &network.IPAM{}
}
if n.Ipam.Driver != "" {
createOpts.IPAM.Driver = n.Ipam.Driver
}
for _, ipamConfig := range n.Ipam.Config {
config := network.IPAMConfig{
Subnet: ipamConfig.Subnet,
}
createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
}
networkEventName := fmt.Sprintf("Network %q", n.Name)
w := progress.ContextWriter(ctx)
w.Event(progress.CreatingEvent(networkEventName))
if _, err := s.apiClient.NetworkCreate(ctx, n.Name, createOpts); err != nil {
w.Event(progress.ErrorEvent(networkEventName))
return errors.Wrapf(err, "failed to create network %s", n.Name)
}
w.Event(progress.CreatedEvent(networkEventName))
return nil
}
return err
}
return nil
}
func (s *composeService) ensureNetworkDown(ctx context.Context, networkID string, networkName string) error {
w := progress.ContextWriter(ctx)
eventName := fmt.Sprintf("Network %q", networkName)
w.Event(progress.RemovingEvent(eventName))
if err := s.apiClient.NetworkRemove(ctx, networkID); err != nil {
w.Event(progress.ErrorEvent(eventName))
return errors.Wrapf(err, fmt.Sprintf("failed to create network %s", networkID))
}
w.Event(progress.RemovedEvent(eventName))
return nil
}
func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig) error {
// TODO could identify volume by label vs name
_, err := s.apiClient.VolumeInspect(ctx, volume.Name)
if err != nil {
if !errdefs.IsNotFound(err) {
return err
}
eventName := fmt.Sprintf("Volume %q", volume.Name)
w := progress.ContextWriter(ctx)
w.Event(progress.CreatingEvent(eventName))
// TODO we miss support for driver_opts and labels
_, err := s.apiClient.VolumeCreate(ctx, volume_api.VolumeCreateBody{
Labels: volume.Labels,
Name: volume.Name,
Driver: volume.Driver,
DriverOpts: volume.DriverOpts,
})
if err != nil {
w.Event(progress.ErrorEvent(eventName))
return err
}
w.Event(progress.CreatedEvent(eventName))
}
return nil
}

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
package local
package compose
import (
"context"

View File

@ -14,15 +14,14 @@
limitations under the License.
*/
package local
package compose
import (
"context"
"testing"
"gotest.tools/v3/assert"
"github.com/compose-spec/compose-go/types"
"gotest.tools/v3/assert"
)
var project = types.Project{

149
local/compose/down.go Normal file
View File

@ -0,0 +1,149 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"context"
"path/filepath"
"strings"
"github.com/docker/compose-cli/progress"
"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"
"golang.org/x/sync/errgroup"
)
func (s *composeService) Down(ctx context.Context, projectName string) error {
eg, _ := errgroup.WithContext(ctx)
w := progress.ContextWriter(ctx)
project, err := s.projectFromContainerLabels(ctx, projectName)
if err != nil {
return err
}
err = InReverseDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
filter := filters.NewArgs(projectFilter(project.Name), serviceFilter(service.Name))
return s.removeContainers(ctx, w, eg, filter)
})
if err != nil {
return err
}
err = eg.Wait()
if err != nil {
return err
}
networks, err := s.apiClient.NetworkList(ctx, moby.NetworkListOptions{
Filters: filters.NewArgs(
projectFilter(projectName),
),
})
if err != nil {
return err
}
for _, n := range networks {
networkID := n.ID
networkName := n.Name
eg.Go(func() error {
return s.ensureNetworkDown(ctx, networkID, networkName)
})
}
return eg.Wait()
}
func (s *composeService) removeContainers(ctx context.Context, w progress.Writer, eg *errgroup.Group, filter filters.Args) error {
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
Filters: filter,
All: true,
})
if err != nil {
return err
}
for _, container := range containers {
eg.Go(func() error {
eventName := "Container " + getContainerName(container)
w.Event(progress.StoppingEvent(eventName))
err := s.apiClient.ContainerStop(ctx, container.ID, nil)
if err != nil {
w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
return err
}
w.Event(progress.RemovingEvent(eventName))
err = s.apiClient.ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{})
if err != nil {
w.Event(progress.ErrorMessageEvent(eventName, "Error while Removing"))
return err
}
w.Event(progress.RemovedEvent(eventName))
return nil
})
}
return nil
}
func (s *composeService) projectFromContainerLabels(ctx context.Context, projectName string) (*types.Project, error) {
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(
projectFilter(projectName),
),
All: true,
})
if err != nil {
return nil, err
}
fakeProject := &types.Project{
Name: projectName,
}
if len(containers) == 0 {
return fakeProject, nil
}
options, err := loadProjectOptionsFromLabels(containers[0])
if err != nil {
return nil, err
}
if options.ConfigPaths[0] == "-" {
for _, container := range containers {
fakeProject.Services = append(fakeProject.Services, types.ServiceConfig{
Name: container.Labels[serviceLabel],
})
}
return fakeProject, nil
}
project, err := cli.ProjectFromOptions(options)
if err != nil {
return nil, err
}
return project, nil
}
func loadProjectOptionsFromLabels(c moby.Container) (*cli.ProjectOptions, error) {
var configFiles []string
relativePathConfigFiles := strings.Split(c.Labels[configFilesLabel], ",")
for _, c := range relativePathConfigFiles {
configFiles = append(configFiles, filepath.Base(c))
}
return cli.NewProjectOptions(configFiles,
cli.WithOsEnv,
cli.WithWorkingDirectory(c.Labels[workingDirLabel]),
cli.WithName(c.Labels[projectLabel]))
}

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
package local
package compose
import (
"fmt"

95
local/compose/logs.go Normal file
View File

@ -0,0 +1,95 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"bytes"
"context"
"io"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/pkg/stdcopy"
"golang.org/x/sync/errgroup"
)
func (s *composeService) Logs(ctx context.Context, projectName string, consumer compose.LogConsumer) error {
list, err := s.apiClient.ContainerList(ctx, types.ContainerListOptions{
Filters: filters.NewArgs(
projectFilter(projectName),
),
})
if err != nil {
return err
}
eg, ctx := errgroup.WithContext(ctx)
for _, c := range list {
service := c.Labels[serviceLabel]
container, err := s.apiClient.ContainerInspect(ctx, c.ID)
if err != nil {
return err
}
eg.Go(func() error {
r, err := s.apiClient.ContainerLogs(ctx, container.ID, types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: true,
})
defer r.Close() // nolint errcheck
if err != nil {
return err
}
w := getWriter(service, container.ID, consumer)
if container.Config.Tty {
_, err = io.Copy(w, r)
} else {
_, err = stdcopy.StdCopy(w, w, r)
}
return err
})
}
return eg.Wait()
}
type splitBuffer struct {
service string
container string
consumer compose.LogConsumer
}
// getWriter creates a io.Writer that will actually split by line and format by LogConsumer
func getWriter(service, container string, l compose.LogConsumer) io.Writer {
return splitBuffer{
service: service,
container: container,
consumer: l,
}
}
func (s splitBuffer) Write(b []byte) (n int, err error) {
split := bytes.Split(b, []byte{'\n'})
for _, line := range split {
if len(line) != 0 {
s.consumer.Log(s.service, s.container, string(line))
}
}
return len(b), nil
}

86
local/compose/ls.go Normal file
View File

@ -0,0 +1,86 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"context"
"fmt"
"sort"
"github.com/docker/compose-cli/api/compose"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
)
func (s *composeService) List(ctx context.Context, projectName string) ([]compose.Stack, error) {
list, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(hasProjectLabelFilter()),
})
if err != nil {
return nil, err
}
return containersToStacks(list)
}
func containersToStacks(containers []moby.Container) ([]compose.Stack, error) {
containersByLabel, keys, err := groupContainerByLabel(containers, projectLabel)
if err != nil {
return nil, err
}
var projects []compose.Stack
for _, project := range keys {
projects = append(projects, compose.Stack{
ID: project,
Name: project,
Status: combinedStatus(containerToState(containersByLabel[project])),
})
}
return projects, nil
}
func containerToState(containers []moby.Container) []string {
statuses := []string{}
for _, c := range containers {
statuses = append(statuses, c.State)
}
return statuses
}
func combinedStatus(statuses []string) string {
nbByStatus := map[string]int{}
keys := []string{}
for _, status := range statuses {
nb, ok := nbByStatus[status]
if !ok {
nb = 0
keys = append(keys, status)
}
nbByStatus[status] = nb + 1
}
sort.Strings(keys)
result := ""
for _, status := range keys {
nb := nbByStatus[status]
if result != "" {
result = result + ", "
}
result = result + fmt.Sprintf("%s(%d)", status, nb)
}
return result
}

85
local/compose/ps.go Normal file
View File

@ -0,0 +1,85 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"context"
"fmt"
"sort"
convert "github.com/docker/compose-cli/local/moby"
"github.com/docker/compose-cli/api/compose"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
)
func (s *composeService) Ps(ctx context.Context, projectName string) ([]compose.ServiceStatus, error) {
list, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
Filters: filters.NewArgs(
projectFilter(projectName),
),
})
if err != nil {
return nil, err
}
return containersToServiceStatus(list)
}
func containersToServiceStatus(containers []moby.Container) ([]compose.ServiceStatus, error) {
containersByLabel, keys, err := groupContainerByLabel(containers, serviceLabel)
if err != nil {
return nil, err
}
var services []compose.ServiceStatus
for _, service := range keys {
containers := containersByLabel[service]
runnningContainers := []moby.Container{}
for _, container := range containers {
if container.State == convert.ContainerRunning {
runnningContainers = append(runnningContainers, container)
}
}
services = append(services, compose.ServiceStatus{
ID: service,
Name: service,
Desired: len(containers),
Replicas: len(runnningContainers),
})
}
return services, nil
}
func groupContainerByLabel(containers []moby.Container, labelName string) (map[string][]moby.Container, []string, error) {
containersByLabel := map[string][]moby.Container{}
keys := []string{}
for _, c := range containers {
label, ok := c.Labels[labelName]
if !ok {
return nil, nil, fmt.Errorf("No label %q set on container %q of compose project", labelName, c.ID)
}
labelContainers, ok := containersByLabel[label]
if !ok {
labelContainers = []moby.Container{}
keys = append(keys, label)
}
labelContainers = append(labelContainers, c)
containersByLabel[label] = labelContainers
}
sort.Strings(keys)
return containersByLabel, keys, nil
}

158
local/compose/pull.go Normal file
View File

@ -0,0 +1,158 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"io"
"strings"
"github.com/compose-spec/compose-go/types"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/distribution/reference"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/registry"
"golang.org/x/sync/errgroup"
"github.com/docker/compose-cli/config"
"github.com/docker/compose-cli/progress"
)
func (s *composeService) Pull(ctx context.Context, project *types.Project) error {
configFile, err := cliconfig.Load(config.Dir(ctx))
if err != nil {
return err
}
info, err := s.apiClient.Info(ctx)
if err != nil {
return err
}
if info.IndexServerAddress == "" {
info.IndexServerAddress = registry.IndexServer
}
w := progress.ContextWriter(ctx)
eg, ctx := errgroup.WithContext(ctx)
for _, srv := range project.Services {
service := srv
eg.Go(func() error {
w.Event(progress.Event{
ID: service.Name,
Status: progress.Working,
Text: "Pulling",
})
ref, err := reference.ParseNormalizedNamed(service.Image)
if err != nil {
return err
}
repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
return err
}
key := repoInfo.Index.Name
if repoInfo.Index.Official {
key = info.IndexServerAddress
}
authConfig, err := configFile.GetAuthConfig(key)
if err != nil {
return err
}
buf, err := json.Marshal(authConfig)
if err != nil {
return err
}
stream, err := s.apiClient.ImagePull(ctx, service.Image, moby.ImagePullOptions{
RegistryAuth: base64.URLEncoding.EncodeToString(buf),
})
if err != nil {
w.Event(progress.Event{
ID: service.Name,
Status: progress.Error,
Text: "Error",
})
return err
}
dec := json.NewDecoder(stream)
for {
var jm jsonmessage.JSONMessage
if err := dec.Decode(&jm); err != nil {
if err == io.EOF {
break
}
return err
}
if jm.Error != nil {
return errors.New(jm.Error.Message)
}
toPullProgressEvent(service.Name, jm, w)
}
w.Event(progress.Event{
ID: service.Name,
Status: progress.Done,
Text: "Pulled",
})
return nil
})
}
return eg.Wait()
}
func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, w progress.Writer) {
if jm.ID == "" || jm.Progress == nil {
return
}
var (
text string
status = progress.Working
)
text = jm.Progress.String()
if jm.Status == "Pull complete" ||
jm.Status == "Already exists" ||
strings.Contains(jm.Status, "Image is up to date") ||
strings.Contains(jm.Status, "Downloaded newer image") {
status = progress.Done
}
if jm.Error != nil {
status = progress.Error
text = jm.Error.Message
}
w.Event(progress.Event{
ID: jm.ID,
ParentID: parent,
Text: jm.Status,
Status: status,
StatusText: text,
})
}

137
local/compose/push.go Normal file
View File

@ -0,0 +1,137 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"github.com/docker/compose-cli/config"
"github.com/docker/compose-cli/progress"
"github.com/compose-spec/compose-go/types"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/distribution/reference"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/registry"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
)
func (s *composeService) Push(ctx context.Context, project *types.Project) error {
configFile, err := cliconfig.Load(config.Dir(ctx))
if err != nil {
return err
}
eg, ctx := errgroup.WithContext(ctx)
info, err := s.apiClient.Info(ctx)
if err != nil {
return err
}
if info.IndexServerAddress == "" {
info.IndexServerAddress = registry.IndexServer
}
for _, service := range project.Services {
if service.Build == nil {
continue
}
service := service
eg.Go(func() error {
w := progress.ContextWriter(ctx)
ref, err := reference.ParseNormalizedNamed(service.Image)
if err != nil {
return err
}
repoInfo, err := registry.ParseRepositoryInfo(ref)
if err != nil {
return err
}
key := repoInfo.Index.Name
if repoInfo.Index.Official {
key = info.IndexServerAddress
}
authConfig, err := configFile.GetAuthConfig(key)
if err != nil {
return err
}
buf, err := json.Marshal(authConfig)
if err != nil {
return err
}
stream, err := s.apiClient.ImagePush(ctx, service.Image, moby.ImagePushOptions{
RegistryAuth: base64.URLEncoding.EncodeToString(buf),
})
if err != nil {
return err
}
dec := json.NewDecoder(stream)
for {
var jm jsonmessage.JSONMessage
if err := dec.Decode(&jm); err != nil {
if err == io.EOF {
break
}
return err
}
if jm.Error != nil {
return errors.New(jm.Error.Message)
}
toPushProgressEvent("Pushing "+service.Name, jm, w)
}
return nil
})
}
return eg.Wait()
}
func toPushProgressEvent(prefix string, jm jsonmessage.JSONMessage, w progress.Writer) {
if jm.ID == "" {
// skipped
return
}
var (
text string
status = progress.Working
)
if jm.Status == "Pull complete" || jm.Status == "Already exists" {
status = progress.Done
}
if jm.Error != nil {
status = progress.Error
text = jm.Error.Message
}
if jm.Progress != nil {
text = jm.Progress.String()
}
w.Event(progress.Event{
ID: fmt.Sprintf("Pushing %s: %s", prefix, jm.ID),
Text: jm.Status,
Status: status,
StatusText: text,
})
}

48
local/compose/start.go Normal file
View File

@ -0,0 +1,48 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"context"
"github.com/docker/compose-cli/api/compose"
"github.com/compose-spec/compose-go/types"
"golang.org/x/sync/errgroup"
)
func (s *composeService) Start(ctx context.Context, project *types.Project, consumer compose.LogConsumer) error {
var group *errgroup.Group
if consumer != nil {
eg, err := s.attach(ctx, project, consumer)
if err != nil {
return err
}
group = eg
}
err := InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
return s.startService(ctx, project, service)
})
if err != nil {
return err
}
if group != nil {
return group.Wait()
}
return nil
}

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
package local
package compose
import (
"encoding/json"

View File

@ -1,62 +0,0 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package local
import (
"io"
moby "github.com/docker/docker/api/types"
)
const (
containerCreated = "created"
containerRestarting = "restarting"
containerRunning = "running"
containerRemoving = "removing" //nolint
containerPaused = "paused" //nolint
containerExited = "exited" //nolint
containerDead = "dead" //nolint
)
var _ io.ReadCloser = containerStdout{}
type containerStdout struct {
moby.HijackedResponse
}
func (l containerStdout) Read(p []byte) (n int, err error) {
return l.Reader.Read(p)
}
func (l containerStdout) Close() error {
l.HijackedResponse.Close()
return nil
}
var _ io.WriteCloser = containerStdin{}
type containerStdin struct {
moby.HijackedResponse
}
func (c containerStdin) Write(p []byte) (n int, err error) {
return c.Conn.Write(p)
}
func (c containerStdin) Close() error {
return c.CloseWrite()
}

View File

@ -35,6 +35,7 @@ import (
"github.com/docker/compose-cli/api/containers"
"github.com/docker/compose-cli/errdefs"
"github.com/docker/compose-cli/local/moby"
)
type containerService struct {
@ -58,8 +59,8 @@ func (cs *containerService) Inspect(ctx context.Context, id string) (containers.
command = strings.Join(c.Config.Cmd, " ")
}
rc := toRuntimeConfig(&c)
hc := toHostConfig(&c)
rc := moby.ToRuntimeConfig(&c)
hc := moby.ToHostConfig(&c)
return containers.Container{
ID: stringid.TruncateID(c.ID),
@ -92,7 +93,7 @@ func (cs *containerService) List(ctx context.Context, all bool) ([]containers.Co
// statuses. We also need to add a `Created` property on the gRPC side.
Status: container.Status,
Command: container.Command,
Ports: toPorts(container.Ports),
Ports: moby.ToPorts(container.Ports),
})
}
@ -100,7 +101,7 @@ func (cs *containerService) List(ctx context.Context, all bool) ([]containers.Co
}
func (cs *containerService) Run(ctx context.Context, r containers.ContainerConfig) error {
exposedPorts, hostBindings, err := fromPorts(r.Ports)
exposedPorts, hostBindings, err := moby.FromPorts(r.Ports)
if err != nil {
return err
}
@ -127,7 +128,7 @@ func (cs *containerService) Run(ctx context.Context, r containers.ContainerConfi
PortBindings: hostBindings,
Mounts: mounts,
AutoRemove: r.AutoRemove,
RestartPolicy: toRestartPolicy(r.RestartPolicyCondition),
RestartPolicy: moby.ToRestartPolicy(r.RestartPolicyCondition),
Resources: container.Resources{
NanoCPUs: int64(r.CPULimit * 1e9),
Memory: int64(r.MemLimit),

75
local/moby/container.go Normal file
View File

@ -0,0 +1,75 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package moby
import (
"io"
moby "github.com/docker/docker/api/types"
)
const (
// ContainerCreated created status
ContainerCreated = "created"
// ContainerRestarting restarting status
ContainerRestarting = "restarting"
// ContainerRunning running status
ContainerRunning = "running"
// ContainerRemoving removing status
ContainerRemoving = "removing" //nolint
// ContainerPaused paused status
ContainerPaused = "paused" //nolint
// ContainerExited exited status
ContainerExited = "exited" //nolint
// ContainerDead dead status
ContainerDead = "dead" //nolint
)
var _ io.ReadCloser = ContainerStdout{}
// ContainerStdout implement ReadCloser for moby.HijackedResponse
type ContainerStdout struct {
moby.HijackedResponse
}
// Read implement io.ReadCloser
func (l ContainerStdout) Read(p []byte) (n int, err error) {
return l.Reader.Read(p)
}
// Close implement io.ReadCloser
func (l ContainerStdout) Close() error {
l.HijackedResponse.Close()
return nil
}
var _ io.WriteCloser = ContainerStdin{}
// ContainerStdin implement WriteCloser for moby.HijackedResponse
type ContainerStdin struct {
moby.HijackedResponse
}
// Write implement io.WriteCloser
func (c ContainerStdin) Write(p []byte) (n int, err error) {
return c.Conn.Write(p)
}
// Close implement io.WriteCloser
func (c ContainerStdin) Close() error {
return c.CloseWrite()
}

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
package local
package moby
import (
"fmt"
@ -31,7 +31,8 @@ import (
"github.com/docker/compose-cli/api/containers"
)
func toRuntimeConfig(m *types.ContainerJSON) *containers.RuntimeConfig {
// ToRuntimeConfig convert into containers.RuntimeConfig
func ToRuntimeConfig(m *types.ContainerJSON) *containers.RuntimeConfig {
if m.Config == nil {
return nil
}
@ -66,7 +67,8 @@ func toRuntimeConfig(m *types.ContainerJSON) *containers.RuntimeConfig {
}
}
func toHostConfig(m *types.ContainerJSON) *containers.HostConfig {
// ToHostConfig convert into containers.HostConfig
func ToHostConfig(m *types.ContainerJSON) *containers.HostConfig {
if m.HostConfig == nil {
return nil
}
@ -79,7 +81,8 @@ func toHostConfig(m *types.ContainerJSON) *containers.HostConfig {
}
}
func toPorts(ports []types.Port) []containers.Port {
// ToPorts convert into containers.Port
func ToPorts(ports []types.Port) []containers.Port {
result := []containers.Port{}
for _, port := range ports {
result = append(result, containers.Port{
@ -93,7 +96,8 @@ func toPorts(ports []types.Port) []containers.Port {
return result
}
func toMobyEnv(environment compose.MappingWithEquals) []string {
// ToMobyEnv convert into []string
func ToMobyEnv(environment compose.MappingWithEquals) []string {
var env []string
for k, v := range environment {
if v == nil {
@ -105,7 +109,8 @@ func toMobyEnv(environment compose.MappingWithEquals) []string {
return env
}
func toMobyHealthCheck(check *compose.HealthCheckConfig) *container.HealthConfig {
// ToMobyHealthCheck convert into container.HealthConfig
func ToMobyHealthCheck(check *compose.HealthCheckConfig) *container.HealthConfig {
if check == nil {
return nil
}
@ -136,7 +141,8 @@ func toMobyHealthCheck(check *compose.HealthCheckConfig) *container.HealthConfig
}
}
func toSeconds(d *compose.Duration) *int {
// ToSeconds convert into seconds
func ToSeconds(d *compose.Duration) *int {
if d == nil {
return nil
}
@ -144,7 +150,8 @@ func toSeconds(d *compose.Duration) *int {
return &s
}
func fromPorts(ports []containers.Port) (map[nat.Port]struct{}, map[nat.Port][]nat.PortBinding, error) {
// FromPorts convert to nat.Port / nat.PortBinding
func FromPorts(ports []containers.Port) (map[nat.Port]struct{}, map[nat.Port][]nat.PortBinding, error) {
var (
exposedPorts = make(map[nat.Port]struct{}, len(ports))
bindings = make(map[nat.Port][]nat.PortBinding)
@ -187,7 +194,8 @@ func fromRestartPolicyName(m string) string {
}
}
func toRestartPolicy(p string) container.RestartPolicy {
// ToRestartPolicy convert to container.RestartPolicy
func ToRestartPolicy(p string) container.RestartPolicy {
switch p {
case containers.RestartPolicyAny:
return container.RestartPolicy{Name: "always"}

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
package local
package moby
import (
"testing"
@ -34,7 +34,7 @@ func TestToRuntimeConfig(t *testing.T) {
Labels: map[string]string{"foo1": "bar1", "foo2": "bar2"},
},
}
rc := toRuntimeConfig(m)
rc := ToRuntimeConfig(m)
res := &containers.RuntimeConfig{
Env: map[string]string{"FOO1": "BAR1", "FOO2": "BAR2"},
Labels: []string{"foo1=bar1", "foo2=bar2"},
@ -63,7 +63,7 @@ func TestToHostConfig(t *testing.T) {
},
ContainerJSONBase: base,
}
hc := toHostConfig(m)
hc := ToHostConfig(m)
res := &containers.HostConfig{
AutoRemove: true,
RestartPolicy: containers.RestartPolicyNone,
@ -96,6 +96,6 @@ func TestToRestartPolicy(t *testing.T) {
{Name: "no"},
}
for i, p := range ours {
assert.Equal(t, toRestartPolicy(p), moby[i])
assert.Equal(t, ToRestartPolicy(p), moby[i])
}
}