Initial commit
This commit is contained in:
33
vendor/github.com/hyperhq/libcompose/docker/auth.go
generated
vendored
Normal file
33
vendor/github.com/hyperhq/libcompose/docker/auth.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/hyperhq/hypercli/registry"
|
||||
)
|
||||
|
||||
// AuthLookup defines a method for looking up authentication information
|
||||
type AuthLookup interface {
|
||||
All() map[string]types.AuthConfig
|
||||
Lookup(repoInfo *registry.RepositoryInfo) types.AuthConfig
|
||||
}
|
||||
|
||||
// ConfigAuthLookup implements AuthLookup by reading a Docker config file
|
||||
type ConfigAuthLookup struct {
|
||||
context *Context
|
||||
}
|
||||
|
||||
// Lookup uses a Docker config file to lookup authentication information
|
||||
func (c *ConfigAuthLookup) Lookup(repoInfo *registry.RepositoryInfo) types.AuthConfig {
|
||||
if c.context.ConfigFile == nil || repoInfo == nil || repoInfo.Index == nil {
|
||||
return types.AuthConfig{}
|
||||
}
|
||||
return registry.ResolveAuthConfig(c.context.ConfigFile.AuthConfigs, repoInfo.Index)
|
||||
}
|
||||
|
||||
// All uses a Docker config file to get all authentication information
|
||||
func (c *ConfigAuthLookup) All() map[string]types.AuthConfig {
|
||||
if c.context.ConfigFile == nil {
|
||||
return map[string]types.AuthConfig{}
|
||||
}
|
||||
return c.context.ConfigFile.AuthConfigs
|
||||
}
|
||||
729
vendor/github.com/hyperhq/libcompose/docker/container.go
generated
vendored
Normal file
729
vendor/github.com/hyperhq/libcompose/docker/container.go
generated
vendored
Normal file
@@ -0,0 +1,729 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/engine-api/client"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/container"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/hyperhq/hypercli/pkg/promise"
|
||||
"github.com/hyperhq/hypercli/pkg/stdcopy"
|
||||
"github.com/hyperhq/hypercli/pkg/stringid"
|
||||
"github.com/hyperhq/hypercli/pkg/term"
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/labels"
|
||||
"github.com/hyperhq/libcompose/logger"
|
||||
"github.com/hyperhq/libcompose/project"
|
||||
"github.com/hyperhq/libcompose/project/events"
|
||||
util "github.com/hyperhq/libcompose/utils"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Container holds information about a docker container and the service it is tied on.
|
||||
type Container struct {
|
||||
name string
|
||||
serviceName string
|
||||
projectName string
|
||||
containerNumber int
|
||||
oneOff bool
|
||||
eventNotifier events.Notifier
|
||||
loggerFactory logger.Factory
|
||||
client client.APIClient
|
||||
|
||||
// FIXME(vdemeester) Remove this dependency
|
||||
service *Service
|
||||
}
|
||||
|
||||
// NewContainer creates a container struct with the specified docker client, name and service.
|
||||
func NewContainer(client client.APIClient, name string, containerNumber int, service *Service) *Container {
|
||||
return &Container{
|
||||
client: client,
|
||||
name: name,
|
||||
containerNumber: containerNumber,
|
||||
|
||||
// TODO(vdemeester) Move these to arguments
|
||||
serviceName: service.name,
|
||||
projectName: service.context.Project.Name,
|
||||
eventNotifier: service.context.Project,
|
||||
loggerFactory: service.context.LoggerFactory,
|
||||
|
||||
// TODO(vdemeester) Remove this dependency
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
// NewOneOffContainer creates a "oneoff" container struct with the specified docker client, name and service.
|
||||
func NewOneOffContainer(client client.APIClient, name string, containerNumber int, service *Service) *Container {
|
||||
c := NewContainer(client, name, containerNumber, service)
|
||||
c.oneOff = true
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Container) findExisting() (*types.ContainerJSON, error) {
|
||||
return GetContainer(c.client, c.name)
|
||||
}
|
||||
|
||||
// Info returns info about the container, like name, command, state or ports.
|
||||
func (c *Container) Info(qFlag bool) (project.Info, error) {
|
||||
container, err := c.findExisting()
|
||||
if err != nil || container == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
infos, err := GetContainersByFilter(c.client, map[string][]string{
|
||||
"name": {container.Name},
|
||||
})
|
||||
if err != nil || len(infos) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
info := infos[0]
|
||||
|
||||
result := project.Info{}
|
||||
if qFlag {
|
||||
result = append(result, project.InfoPart{Key: "Id", Value: container.ID})
|
||||
} else {
|
||||
result = append(result, project.InfoPart{Key: "Name", Value: name(info.Names)})
|
||||
result = append(result, project.InfoPart{Key: "Command", Value: info.Command})
|
||||
result = append(result, project.InfoPart{Key: "State", Value: info.Status})
|
||||
result = append(result, project.InfoPart{Key: "Ports", Value: portString(info.Ports)})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func portString(ports []types.Port) string {
|
||||
result := []string{}
|
||||
|
||||
for _, port := range ports {
|
||||
if port.PublicPort > 0 {
|
||||
result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
|
||||
} else {
|
||||
result = append(result, fmt.Sprintf("%d/%s", port.PrivatePort, port.Type))
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(result, ", ")
|
||||
}
|
||||
|
||||
func name(names []string) string {
|
||||
max := math.MaxInt32
|
||||
var current string
|
||||
|
||||
for _, v := range names {
|
||||
if len(v) < max {
|
||||
max = len(v)
|
||||
current = v
|
||||
}
|
||||
}
|
||||
|
||||
return current[1:]
|
||||
}
|
||||
|
||||
// Recreate will not refresh the container by means of relaxation and enjoyment,
|
||||
// just delete it and create a new one with the current configuration
|
||||
func (c *Container) Recreate(imageName string) (*types.ContainerJSON, error) {
|
||||
container, err := c.findExisting()
|
||||
if err != nil || container == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hash := container.Config.Labels[labels.HASH.Str()]
|
||||
if hash == "" {
|
||||
return nil, fmt.Errorf("Failed to find hash on old container: %s", container.Name)
|
||||
}
|
||||
|
||||
name := container.Name[1:]
|
||||
newName := fmt.Sprintf("%s-%s", name, container.ID[:12])
|
||||
logrus.Debugf("Renaming %s => %s", name, newName)
|
||||
if err := c.client.ContainerRename(context.Background(), container.ID, newName); err != nil {
|
||||
logrus.Errorf("Failed to rename old container %s", c.name)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newContainer, err := c.createContainer(imageName, container.ID, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logrus.Debugf("Created replacement container %s", newContainer.ID)
|
||||
|
||||
if _, err := c.client.ContainerRemove(context.Background(), container.ID, types.ContainerRemoveOptions{
|
||||
Force: true,
|
||||
RemoveVolumes: false,
|
||||
}); err != nil {
|
||||
logrus.Errorf("Failed to remove old container %s", c.name)
|
||||
return nil, err
|
||||
}
|
||||
logrus.Debugf("Removed old container %s %s", c.name, container.ID)
|
||||
|
||||
return newContainer, nil
|
||||
}
|
||||
|
||||
// Create creates the container based on the specified image name and send an event
|
||||
// to notify the container has been created. If the container already exists, does
|
||||
// nothing.
|
||||
func (c *Container) Create(imageName string) (*types.ContainerJSON, error) {
|
||||
return c.CreateWithOverride(imageName, nil)
|
||||
}
|
||||
|
||||
// CreateWithOverride create container and override parts of the config to
|
||||
// allow special situations to override the config generated from the compose
|
||||
// file
|
||||
func (c *Container) CreateWithOverride(imageName string, configOverride *config.ServiceConfig) (*types.ContainerJSON, error) {
|
||||
container, err := c.findExisting()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if container == nil {
|
||||
container, err = c.createContainer(imageName, "", configOverride)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.eventNotifier.Notify(events.ContainerCreated, c.serviceName, map[string]string{
|
||||
"name": c.Name(),
|
||||
})
|
||||
}
|
||||
|
||||
return container, err
|
||||
}
|
||||
|
||||
// Stop stops the container.
|
||||
func (c *Container) Stop(timeout int) error {
|
||||
return c.withContainer(func(container *types.ContainerJSON) error {
|
||||
return c.client.ContainerStop(context.Background(), container.ID, timeout)
|
||||
})
|
||||
}
|
||||
|
||||
// Pause pauses the container. If the containers are already paused, don't fail.
|
||||
func (c *Container) Pause() error {
|
||||
return c.withContainer(func(container *types.ContainerJSON) error {
|
||||
if !container.State.Paused {
|
||||
return c.client.ContainerPause(context.Background(), container.ID)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Unpause unpauses the container. If the containers are not paused, don't fail.
|
||||
func (c *Container) Unpause() error {
|
||||
return c.withContainer(func(container *types.ContainerJSON) error {
|
||||
if container.State.Paused {
|
||||
return c.client.ContainerUnpause(context.Background(), container.ID)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Kill kill the container.
|
||||
func (c *Container) Kill(signal string) error {
|
||||
return c.withContainer(func(container *types.ContainerJSON) error {
|
||||
return c.client.ContainerKill(context.Background(), container.ID, signal)
|
||||
})
|
||||
}
|
||||
|
||||
// Delete removes the container if existing. If the container is running, it tries
|
||||
// to stop it first.
|
||||
func (c *Container) Delete(removeVolume bool) error {
|
||||
container, err := c.findExisting()
|
||||
if err != nil || container == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := c.client.ContainerInspect(context.Background(), container.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.State.Running {
|
||||
_, err := c.client.ContainerRemove(context.Background(), container.ID, types.ContainerRemoveOptions{
|
||||
Force: true,
|
||||
RemoveVolumes: removeVolume,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsRunning returns the running state of the container.
|
||||
func (c *Container) IsRunning() (bool, error) {
|
||||
container, err := c.findExisting()
|
||||
if err != nil || container == nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
info, err := c.client.ContainerInspect(context.Background(), container.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return info.State.Running, nil
|
||||
}
|
||||
|
||||
// Run creates, start and attach to the container based on the image name,
|
||||
// the specified configuration.
|
||||
// It will always create a new container.
|
||||
func (c *Container) Run(ctx context.Context, imageName string, configOverride *config.ServiceConfig) (int, error) {
|
||||
var (
|
||||
errCh chan error
|
||||
out, stderr io.Writer
|
||||
in io.ReadCloser
|
||||
)
|
||||
|
||||
container, err := c.createContainer(imageName, "", configOverride)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if configOverride.StdinOpen {
|
||||
in = os.Stdin
|
||||
}
|
||||
if configOverride.Tty {
|
||||
out = os.Stdout
|
||||
}
|
||||
if configOverride.Tty {
|
||||
stderr = os.Stderr
|
||||
}
|
||||
|
||||
options := types.ContainerAttachOptions{
|
||||
Stream: true,
|
||||
Stdin: configOverride.StdinOpen,
|
||||
Stdout: configOverride.Tty,
|
||||
Stderr: configOverride.Tty,
|
||||
}
|
||||
|
||||
resp, err := c.client.ContainerAttach(ctx, container.ID, options)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// set raw terminal
|
||||
inFd, _ := term.GetFdInfo(in)
|
||||
state, err := term.SetRawTerminal(inFd)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
// restore raw terminal
|
||||
defer term.RestoreTerminal(inFd, state)
|
||||
// holdHijackedConnection (in goroutine)
|
||||
errCh = promise.Go(func() error {
|
||||
return holdHijackedConnection(configOverride.Tty, in, out, stderr, resp)
|
||||
})
|
||||
|
||||
if err := c.client.ContainerStart(ctx, container.ID, ""); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if err := <-errCh; err != nil {
|
||||
logrus.Debugf("Error hijack: %s", err)
|
||||
return -1, err
|
||||
}
|
||||
|
||||
var status int
|
||||
// Attached mode
|
||||
if c.service.context.Autoremove {
|
||||
// Warn user if they detached us
|
||||
js, err := c.client.ContainerInspect(ctx, container.ID)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if js.State.Running == true || js.State.Paused == true {
|
||||
logrus.Infof("Detached from %s, awaiting its termination in order to uphold \"--rm\".\n",
|
||||
stringid.TruncateID(container.ID))
|
||||
}
|
||||
|
||||
// Autoremove: wait for the container to finish, retrieve
|
||||
// the exit code and remove the container
|
||||
if status, err = c.client.ContainerWait(ctx, container.ID); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
exitedContainer, err := c.client.ContainerInspect(ctx, container.ID)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
status = exitedContainer.State.ExitCode
|
||||
} else {
|
||||
// No Autoremove: Simply retrieve the exit code
|
||||
if !configOverride.Tty {
|
||||
// In non-TTY mode, we can't detach, so we must wait for container exit
|
||||
if status, err = c.client.ContainerWait(ctx, container.ID); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
} else {
|
||||
// In TTY mode, there is a race: if the process dies too slowly, the state could
|
||||
// be updated after the getExitCode call and result in the wrong exit code being reported
|
||||
exitedContainer, err := c.client.ContainerInspect(ctx, container.ID)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
status = exitedContainer.State.ExitCode
|
||||
}
|
||||
}
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func holdHijackedConnection(tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
|
||||
var err error
|
||||
receiveStdout := make(chan error, 1)
|
||||
if outputStream != nil || errorStream != nil {
|
||||
go func() {
|
||||
// When TTY is ON, use regular copy
|
||||
if tty && outputStream != nil {
|
||||
_, err = io.Copy(outputStream, resp.Reader)
|
||||
} else {
|
||||
_, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader)
|
||||
}
|
||||
logrus.Debugf("[hijack] End of stdout")
|
||||
receiveStdout <- err
|
||||
}()
|
||||
}
|
||||
|
||||
stdinDone := make(chan struct{})
|
||||
go func() {
|
||||
if inputStream != nil {
|
||||
io.Copy(resp.Conn, inputStream)
|
||||
logrus.Debugf("[hijack] End of stdin")
|
||||
}
|
||||
|
||||
if err := resp.CloseWrite(); err != nil {
|
||||
logrus.Debugf("Couldn't send EOF: %s", err)
|
||||
}
|
||||
close(stdinDone)
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-receiveStdout:
|
||||
if err != nil {
|
||||
logrus.Debugf("Error receiveStdout: %s", err)
|
||||
return err
|
||||
}
|
||||
case <-stdinDone:
|
||||
if outputStream != nil || errorStream != nil {
|
||||
if err := <-receiveStdout; err != nil {
|
||||
logrus.Debugf("Error receiveStdout: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Up creates and start the container based on the image name and send an event
|
||||
// to notify the container has been created. If the container exists but is stopped
|
||||
// it tries to start it.
|
||||
func (c *Container) Up(imageName string) error {
|
||||
var err error
|
||||
|
||||
container, err := c.Create(imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !container.State.Running {
|
||||
c.Start(container)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start the specified container with the specified host config
|
||||
func (c *Container) Start(container *types.ContainerJSON) error {
|
||||
logrus.WithFields(logrus.Fields{"container.ID": container.ID, "c.name": c.name}).Debug("Starting container")
|
||||
if err := c.client.ContainerStart(context.Background(), container.ID, ""); err != nil {
|
||||
logrus.WithFields(logrus.Fields{"container.ID": container.ID, "c.name": c.name}).Debug("Failed to start container")
|
||||
return err
|
||||
}
|
||||
c.eventNotifier.Notify(events.ContainerStarted, c.serviceName, map[string]string{
|
||||
"name": c.Name(),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// OutOfSync checks if the container is out of sync with the service definition.
|
||||
// It looks if the the service hash container label is the same as the computed one.
|
||||
func (c *Container) OutOfSync(imageName string) (bool, error) {
|
||||
container, err := c.findExisting()
|
||||
if err != nil || container == nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if container.Config.Image != imageName {
|
||||
logrus.Debugf("Images for %s do not match %s!=%s", c.name, container.Config.Image, imageName)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if container.Config.Labels[labels.HASH.Str()] != c.getHash() {
|
||||
logrus.Debugf("Hashes for %s do not match %s!=%s", c.name, container.Config.Labels[labels.HASH.Str()], c.getHash())
|
||||
return true, nil
|
||||
}
|
||||
|
||||
image, _, err := c.client.ImageInspectWithRaw(context.Background(), container.Config.Image, false)
|
||||
if err != nil {
|
||||
if client.IsErrImageNotFound(err) {
|
||||
logrus.Debugf("Image %s do not exist, do not know if it's out of sync", container.Config.Image)
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
logrus.Debugf("Checking existing image name vs id: %s == %s", image.ID, container.Image)
|
||||
return image.ID != container.Image, err
|
||||
}
|
||||
|
||||
func (c *Container) getHash() string {
|
||||
return config.GetServiceHash(c.serviceName, c.service.Config())
|
||||
}
|
||||
|
||||
func volumeBinds(volumes map[string]struct{}, container *types.ContainerJSON) []string {
|
||||
result := make([]string, 0, len(container.Mounts))
|
||||
for _, mount := range container.Mounts {
|
||||
if _, ok := volumes[mount.Destination]; ok {
|
||||
result = append(result, fmt.Sprint(mount.Source, ":", mount.Destination))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *Container) createContainer(imageName, oldContainer string, configOverride *config.ServiceConfig) (*types.ContainerJSON, error) {
|
||||
serviceConfig := c.service.serviceConfig
|
||||
if configOverride != nil {
|
||||
serviceConfig.Command = configOverride.Command
|
||||
serviceConfig.Tty = configOverride.Tty
|
||||
serviceConfig.StdinOpen = configOverride.StdinOpen
|
||||
}
|
||||
configWrapper, err := ConvertToAPI(c.service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
configWrapper.Config.Image = imageName
|
||||
|
||||
if configWrapper.Config.Labels == nil {
|
||||
configWrapper.Config.Labels = map[string]string{}
|
||||
}
|
||||
|
||||
oneOffString := "False"
|
||||
if c.oneOff {
|
||||
oneOffString = "True"
|
||||
}
|
||||
|
||||
configWrapper.Config.Labels[labels.SERVICE.Str()] = c.serviceName
|
||||
configWrapper.Config.Labels[labels.PROJECT.Str()] = c.projectName
|
||||
configWrapper.Config.Labels[labels.HASH.Str()] = c.getHash()
|
||||
configWrapper.Config.Labels[labels.ONEOFF.Str()] = oneOffString
|
||||
configWrapper.Config.Labels[labels.NUMBER.Str()] = fmt.Sprint(c.containerNumber)
|
||||
configWrapper.Config.Labels[labels.VERSION.Str()] = ComposeVersion
|
||||
size := "s4"
|
||||
if serviceConfig.Size != "" {
|
||||
size = serviceConfig.Size
|
||||
}
|
||||
configWrapper.Config.Labels["sh_hyper_instancetype"] = size
|
||||
|
||||
err = c.populateAdditionalHostConfig(configWrapper.HostConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if oldContainer != "" {
|
||||
info, err := c.client.ContainerInspect(context.Background(), oldContainer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configWrapper.HostConfig.Binds = util.Merge(configWrapper.HostConfig.Binds, volumeBinds(configWrapper.Config.Volumes, &info))
|
||||
}
|
||||
|
||||
logrus.Debugf("Creating container %s %#v", c.name, configWrapper)
|
||||
|
||||
container, err := c.client.ContainerCreate(context.Background(), configWrapper.Config, configWrapper.HostConfig, configWrapper.NetworkingConfig, c.name)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to create container %s: %v", c.name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return GetContainer(c.client, container.ID)
|
||||
}
|
||||
|
||||
func (c *Container) populateAdditionalHostConfig(hostConfig *container.HostConfig) error {
|
||||
links := map[string]string{}
|
||||
|
||||
for _, link := range c.service.DependentServices() {
|
||||
if !c.service.context.Project.ServiceConfigs.Has(link.Target) {
|
||||
continue
|
||||
}
|
||||
|
||||
service, err := c.service.context.Project.CreateService(link.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
containers, err := service.Containers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if link.Type == project.RelTypeLink {
|
||||
c.addLinks(links, service, link, containers)
|
||||
} else if link.Type == project.RelTypeIpcNamespace {
|
||||
hostConfig, err = c.addIpc(hostConfig, service, containers)
|
||||
} else if link.Type == project.RelTypeNetNamespace {
|
||||
hostConfig, err = c.addNetNs(hostConfig, service, containers)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
hostConfig.Links = []string{}
|
||||
for k, v := range links {
|
||||
hostConfig.Links = append(hostConfig.Links, strings.Join([]string{v, k}, ":"))
|
||||
}
|
||||
for _, v := range c.service.Config().ExternalLinks {
|
||||
hostConfig.Links = append(hostConfig.Links, v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) addLinks(links map[string]string, service project.Service, rel project.ServiceRelationship, containers []project.Container) {
|
||||
for _, container := range containers {
|
||||
if _, ok := links[rel.Alias]; !ok {
|
||||
links[rel.Alias] = container.Name()
|
||||
}
|
||||
|
||||
links[container.Name()] = container.Name()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Container) addIpc(config *container.HostConfig, service project.Service, containers []project.Container) (*container.HostConfig, error) {
|
||||
/*
|
||||
if len(containers) == 0 {
|
||||
return nil, fmt.Errorf("Failed to find container for IPC %v", c.service.Config().Ipc)
|
||||
}
|
||||
|
||||
id, err := containers[0].ID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.IpcMode = container.IpcMode("container:" + id)
|
||||
*/
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (c *Container) addNetNs(config *container.HostConfig, service project.Service, containers []project.Container) (*container.HostConfig, error) {
|
||||
/*
|
||||
if len(containers) == 0 {
|
||||
return nil, fmt.Errorf("Failed to find container for networks ns %v", c.service.Config().NetworkMode)
|
||||
}
|
||||
|
||||
id, err := containers[0].ID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.NetworkMode = container.NetworkMode("container:" + id)
|
||||
*/
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// ID returns the container Id.
|
||||
func (c *Container) ID() (string, error) {
|
||||
container, err := c.findExisting()
|
||||
if container == nil {
|
||||
return "", err
|
||||
}
|
||||
return container.ID, err
|
||||
}
|
||||
|
||||
// Name returns the container name.
|
||||
func (c *Container) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
// Restart restarts the container if existing, does nothing otherwise.
|
||||
func (c *Container) Restart(timeout int) error {
|
||||
container, err := c.findExisting()
|
||||
if err != nil || container == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.client.ContainerRestart(context.Background(), container.ID, timeout)
|
||||
}
|
||||
|
||||
// Log forwards container logs to the project configured logger.
|
||||
func (c *Container) Log(follow bool) error {
|
||||
container, err := c.findExisting()
|
||||
if container == nil || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := c.client.ContainerInspect(context.Background(), container.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME(vdemeester) update container struct to do less API calls
|
||||
name := fmt.Sprintf("%s-%d", c.service.name, c.containerNumber)
|
||||
l := c.loggerFactory.Create(name)
|
||||
|
||||
options := types.ContainerLogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Follow: follow,
|
||||
Tail: "all",
|
||||
}
|
||||
responseBody, err := c.client.ContainerLogs(context.Background(), container.ID, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer responseBody.Close()
|
||||
|
||||
if info.Config.Tty {
|
||||
_, err = io.Copy(&logger.Wrapper{Logger: l}, responseBody)
|
||||
} else {
|
||||
_, err = stdcopy.StdCopy(&logger.Wrapper{Logger: l}, &logger.Wrapper{Logger: l, Err: true}, responseBody)
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{"Logger": l, "err": err}).Debug("c.client.Logs() returned error")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Container) withContainer(action func(*types.ContainerJSON) error) error {
|
||||
container, err := c.findExisting()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if container != nil {
|
||||
return action(container)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Port returns the host port the specified port is mapped on.
|
||||
func (c *Container) Port(port string) (string, error) {
|
||||
container, err := c.findExisting()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if bindings, ok := container.NetworkSettings.Ports[nat.Port(port)]; ok {
|
||||
result := []string{}
|
||||
for _, binding := range bindings {
|
||||
result = append(result, binding.HostIP+":"+binding.HostPort)
|
||||
}
|
||||
|
||||
return strings.Join(result, "\n"), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
36
vendor/github.com/hyperhq/libcompose/docker/context.go
generated
vendored
Normal file
36
vendor/github.com/hyperhq/libcompose/docker/context.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/hyperhq/hypercli/cliconfig"
|
||||
"github.com/hyperhq/libcompose/project"
|
||||
)
|
||||
|
||||
// Context holds context meta information about a libcompose project and docker
|
||||
// client information (like configuration file, builder to use, …)
|
||||
type Context struct {
|
||||
project.Context
|
||||
ClientFactory project.ClientFactory
|
||||
ConfigDir string
|
||||
ConfigFile *cliconfig.ConfigFile
|
||||
AuthLookup AuthLookup
|
||||
}
|
||||
|
||||
func (c *Context) open() error {
|
||||
return c.LookupConfig()
|
||||
}
|
||||
|
||||
// LookupConfig tries to load the docker configuration files, if any.
|
||||
func (c *Context) LookupConfig() error {
|
||||
if c.ConfigFile != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
config, err := cliconfig.Load(c.ConfigDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.ConfigFile = config
|
||||
|
||||
return nil
|
||||
}
|
||||
257
vendor/github.com/hyperhq/libcompose/docker/convert.go
generated
vendored
Normal file
257
vendor/github.com/hyperhq/libcompose/docker/convert.go
generated
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/engine-api/types/container"
|
||||
"github.com/docker/engine-api/types/network"
|
||||
"github.com/docker/engine-api/types/strslice"
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/project"
|
||||
"github.com/hyperhq/libcompose/utils"
|
||||
"github.com/hyperhq/hypercli/runconfig/opts"
|
||||
)
|
||||
|
||||
// ConfigWrapper wraps Config, HostConfig and NetworkingConfig for a container.
|
||||
type ConfigWrapper struct {
|
||||
Config *container.Config
|
||||
HostConfig *container.HostConfig
|
||||
NetworkingConfig *network.NetworkingConfig
|
||||
}
|
||||
|
||||
// Filter filters the specified string slice with the specified function.
|
||||
func Filter(vs []string, f func(string) bool) []string {
|
||||
r := make([]string, 0, len(vs))
|
||||
for _, v := range vs {
|
||||
if f(v) {
|
||||
r = append(r, v)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func isBind(s string) bool {
|
||||
return strings.ContainsRune(s, ':')
|
||||
}
|
||||
|
||||
func isVolume(s string) bool {
|
||||
return !isBind(s)
|
||||
}
|
||||
|
||||
// ConvertToAPI converts a service configuration to a docker API container configuration.
|
||||
func ConvertToAPI(s *Service) (*ConfigWrapper, error) {
|
||||
config, hostConfig, err := Convert(s.serviceConfig, s.context.Context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := ConfigWrapper{
|
||||
Config: config,
|
||||
HostConfig: hostConfig,
|
||||
NetworkingConfig: &network.NetworkingConfig{},
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func isNamedVolume(volume string) bool {
|
||||
return !strings.HasPrefix(volume, ".") && !strings.HasPrefix(volume, "/") && !strings.HasPrefix(volume, "~")
|
||||
}
|
||||
|
||||
func volumes(c *config.ServiceConfig, ctx project.Context) map[string]struct{} {
|
||||
volumes := make(map[string]struct{}, len(c.Volumes))
|
||||
for k, v := range c.Volumes {
|
||||
if len(ctx.ComposeFiles) > 0 && !isNamedVolume(v) {
|
||||
v = ctx.ResourceLookup.ResolvePath(v, ctx.ComposeFiles[0])
|
||||
}
|
||||
|
||||
c.Volumes[k] = v
|
||||
if isVolume(v) {
|
||||
volumes[v] = struct{}{}
|
||||
}
|
||||
}
|
||||
return volumes
|
||||
}
|
||||
|
||||
func restartPolicy(c *config.ServiceConfig) (*container.RestartPolicy, error) {
|
||||
restart, err := opts.ParseRestartPolicy(c.Restart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &container.RestartPolicy{Name: restart.Name, MaximumRetryCount: restart.MaximumRetryCount}, nil
|
||||
}
|
||||
|
||||
/*
|
||||
func ports(c *config.ServiceConfig) (map[nat.Port]struct{}, nat.PortMap, error) {
|
||||
ports, binding, err := nat.ParsePortSpecs(c.Ports)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
exPorts, _, err := nat.ParsePortSpecs(c.Expose)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for k, v := range exPorts {
|
||||
ports[k] = v
|
||||
}
|
||||
|
||||
exposedPorts := map[nat.Port]struct{}{}
|
||||
for k, v := range ports {
|
||||
exposedPorts[nat.Port(k)] = v
|
||||
}
|
||||
|
||||
portBindings := nat.PortMap{}
|
||||
for k, bv := range binding {
|
||||
dcbs := make([]nat.PortBinding, len(bv))
|
||||
for k, v := range bv {
|
||||
dcbs[k] = nat.PortBinding{HostIP: v.HostIP, HostPort: v.HostPort}
|
||||
}
|
||||
portBindings[nat.Port(k)] = dcbs
|
||||
}
|
||||
return exposedPorts, portBindings, nil
|
||||
}
|
||||
*/
|
||||
|
||||
// Convert converts a service configuration to an docker API structures (Config and HostConfig)
|
||||
func Convert(c *config.ServiceConfig, ctx project.Context) (*container.Config, *container.HostConfig, error) {
|
||||
restartPolicy, err := restartPolicy(c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
/*
|
||||
exposedPorts, portBindings, err := ports(c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
deviceMappings, err := parseDevices(c.Devices)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var volumesFrom []string
|
||||
if c.VolumesFrom != nil {
|
||||
volumesFrom, err = getVolumesFrom(c.VolumesFrom, ctx.Project.ServiceConfigs, ctx.ProjectName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
*/
|
||||
config := &container.Config{
|
||||
Entrypoint: strslice.StrSlice(utils.CopySlice(c.Entrypoint)),
|
||||
Hostname: c.Hostname,
|
||||
Domainname: c.DomainName,
|
||||
//User: c.User,
|
||||
Env: utils.CopySlice(c.Environment),
|
||||
Cmd: strslice.StrSlice(utils.CopySlice(c.Command)),
|
||||
Image: c.Image,
|
||||
Labels: utils.CopyMap(c.Labels),
|
||||
//ExposedPorts: exposedPorts,
|
||||
Tty: c.Tty,
|
||||
OpenStdin: c.StdinOpen,
|
||||
WorkingDir: c.WorkingDir,
|
||||
Volumes: volumes(c, ctx),
|
||||
//MacAddress: c.MacAddress,
|
||||
}
|
||||
|
||||
/*
|
||||
ulimits := []*units.Ulimit{}
|
||||
if c.Ulimits.Elements != nil {
|
||||
for _, ulimit := range c.Ulimits.Elements {
|
||||
ulimits = append(ulimits, &units.Ulimit{
|
||||
Name: ulimit.Name,
|
||||
Soft: ulimit.Soft,
|
||||
Hard: ulimit.Hard,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
resources := container.Resources{
|
||||
CgroupParent: c.CgroupParent,
|
||||
Memory: c.MemLimit,
|
||||
MemorySwap: c.MemSwapLimit,
|
||||
CPUShares: c.CPUShares,
|
||||
CPUQuota: c.CPUQuota,
|
||||
CpusetCpus: c.CPUSet,
|
||||
Ulimits: ulimits,
|
||||
Devices: deviceMappings,
|
||||
}
|
||||
|
||||
dns := strslice.StrSlice(c.DNS)
|
||||
dnsSearch := strslice.StrSlice(c.DNSSearch)
|
||||
*/
|
||||
hostConfig := &container.HostConfig{
|
||||
/*
|
||||
VolumesFrom: volumesFrom,
|
||||
CapAdd: strslice.New(c.CapAdd...),
|
||||
CapDrop: strslice.New(c.CapDrop...),
|
||||
ExtraHosts: utils.CopySlice(c.ExtraHosts),
|
||||
Privileged: c.Privileged,
|
||||
*/
|
||||
Binds: Filter(c.Volumes, isBind),
|
||||
/*
|
||||
DNS: (&dns).Slice(),
|
||||
DNSSearch: (&dnsSearch).Slice(),
|
||||
LogConfig: container.LogConfig{
|
||||
Type: c.Logging.Driver,
|
||||
Config: utils.CopyMap(c.Logging.Options),
|
||||
},
|
||||
*/
|
||||
NetworkMode: "bridge",
|
||||
/*
|
||||
ReadonlyRootfs: c.ReadOnly,
|
||||
PidMode: container.PidMode(c.Pid),
|
||||
UTSMode: container.UTSMode(c.Uts),
|
||||
IpcMode: container.IpcMode(c.Ipc),
|
||||
PortBindings: portBindings,
|
||||
*/
|
||||
RestartPolicy: *restartPolicy,
|
||||
/*
|
||||
SecurityOpt: utils.CopySlice(c.SecurityOpt),
|
||||
VolumeDriver: c.VolumeDriver,
|
||||
Resources: resources,
|
||||
*/
|
||||
}
|
||||
|
||||
return config, hostConfig, nil
|
||||
}
|
||||
|
||||
/*
|
||||
func getVolumesFrom(volumesFrom []string, serviceConfigs *config.ServiceConfigs, projectName string) ([]string, error) {
|
||||
volumes := []string{}
|
||||
for _, volumeFrom := range volumesFrom {
|
||||
if serviceConfig, ok := serviceConfigs.Get(volumeFrom); ok {
|
||||
// It's a service - Use the first one
|
||||
name := fmt.Sprintf("%s_%s_1", projectName, volumeFrom)
|
||||
// If a container name is specified, use that instead
|
||||
if serviceConfig.ContainerName != "" {
|
||||
name = serviceConfig.ContainerName
|
||||
}
|
||||
volumes = append(volumes, name)
|
||||
} else {
|
||||
volumes = append(volumes, volumeFrom)
|
||||
}
|
||||
}
|
||||
return volumes, nil
|
||||
}
|
||||
|
||||
func parseDevices(devices []string) ([]container.DeviceMapping, error) {
|
||||
// parse device mappings
|
||||
deviceMappings := []container.DeviceMapping{}
|
||||
for _, device := range devices {
|
||||
v, err := opts.ParseDevice(device)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deviceMappings = append(deviceMappings, container.DeviceMapping{
|
||||
PathOnHost: v.PathOnHost,
|
||||
PathInContainer: v.PathInContainer,
|
||||
CgroupPermissions: v.CgroupPermissions,
|
||||
})
|
||||
}
|
||||
|
||||
return deviceMappings, nil
|
||||
}
|
||||
*/
|
||||
58
vendor/github.com/hyperhq/libcompose/docker/convert_test.go
generated
vendored
Normal file
58
vendor/github.com/hyperhq/libcompose/docker/convert_test.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
shlex "github.com/flynn/go-shlex"
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/lookup"
|
||||
"github.com/hyperhq/libcompose/yaml"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseCommand(t *testing.T) {
|
||||
exp := []string{"sh", "-c", "exec /opt/bin/flanneld -logtostderr=true -iface=${NODE_IP}"}
|
||||
cmd, err := shlex.Split("sh -c 'exec /opt/bin/flanneld -logtostderr=true -iface=${NODE_IP}'")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, exp, cmd)
|
||||
}
|
||||
|
||||
func TestParseBindsAndVolumes(t *testing.T) {
|
||||
ctx := &Context{}
|
||||
ctx.ComposeFiles = []string{"foo/docker-compose.yml"}
|
||||
ctx.ResourceLookup = &lookup.FileConfigLookup{}
|
||||
|
||||
abs, err := filepath.Abs(".")
|
||||
assert.Nil(t, err)
|
||||
cfg, hostCfg, err := Convert(&config.ServiceConfig{
|
||||
Volumes: []string{"/foo", "/home:/home", "/bar/baz", ".:/home", "/usr/lib:/usr/lib:ro"},
|
||||
}, ctx.Context)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, map[string]struct{}{"/foo": {}, "/bar/baz": {}}, cfg.Volumes)
|
||||
assert.Equal(t, []string{"/home:/home", abs + "/foo:/home", "/usr/lib:/usr/lib:ro"}, hostCfg.Binds)
|
||||
}
|
||||
|
||||
func TestParseLabels(t *testing.T) {
|
||||
ctx := &Context{}
|
||||
ctx.ComposeFiles = []string{"foo/docker-compose.yml"}
|
||||
ctx.ResourceLookup = &lookup.FileConfigLookup{}
|
||||
bashCmd := "bash"
|
||||
fooLabel := "foo.label"
|
||||
fooLabelValue := "service.config.value"
|
||||
sc := &config.ServiceConfig{
|
||||
Entrypoint: yaml.Command([]string{bashCmd}),
|
||||
Labels: yaml.SliceorMap{fooLabel: "service.config.value"},
|
||||
}
|
||||
cfg, _, err := Convert(sc, ctx.Context)
|
||||
assert.Nil(t, err)
|
||||
|
||||
cfg.Labels[fooLabel] = "FUN"
|
||||
cfg.Entrypoint[0] = "less"
|
||||
|
||||
assert.Equal(t, fooLabelValue, sc.Labels[fooLabel])
|
||||
assert.Equal(t, "FUN", cfg.Labels[fooLabel])
|
||||
|
||||
assert.Equal(t, yaml.Command{bashCmd}, sc.Entrypoint)
|
||||
assert.Equal(t, []string{"less"}, []string(cfg.Entrypoint))
|
||||
}
|
||||
41
vendor/github.com/hyperhq/libcompose/docker/functions.go
generated
vendored
Normal file
41
vendor/github.com/hyperhq/libcompose/docker/functions.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/docker/engine-api/client"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// GetContainersByFilter looks up the hosts containers with the specified filters and
|
||||
// returns a list of container matching it, or an error.
|
||||
func GetContainersByFilter(clientInstance client.APIClient, containerFilters ...map[string][]string) ([]types.Container, error) {
|
||||
filterArgs := filters.NewArgs()
|
||||
|
||||
// FIXME(vdemeester) I don't like 3 for loops >_<
|
||||
for _, filter := range containerFilters {
|
||||
for key, filterValue := range filter {
|
||||
for _, value := range filterValue {
|
||||
filterArgs.Add(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clientInstance.ContainerList(context.Background(), types.ContainerListOptions{
|
||||
All: true,
|
||||
Filter: filterArgs,
|
||||
})
|
||||
}
|
||||
|
||||
// GetContainer looks up the hosts containers with the specified ID
|
||||
// or name and returns it, or an error.
|
||||
func GetContainer(clientInstance client.APIClient, id string) (*types.ContainerJSON, error) {
|
||||
container, err := clientInstance.ContainerInspect(context.Background(), id)
|
||||
if err != nil {
|
||||
if client.IsErrContainerNotFound(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &container, nil
|
||||
}
|
||||
80
vendor/github.com/hyperhq/libcompose/docker/image.go
generated
vendored
Normal file
80
vendor/github.com/hyperhq/libcompose/docker/image.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/engine-api/client"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/hyperhq/hypercli/pkg/jsonmessage"
|
||||
"github.com/hyperhq/hypercli/pkg/term"
|
||||
"github.com/hyperhq/hypercli/reference"
|
||||
"github.com/hyperhq/hypercli/registry"
|
||||
)
|
||||
|
||||
func removeImage(client client.APIClient, image string) error {
|
||||
_, err := client.ImageRemove(context.Background(), image, types.ImageRemoveOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
func pullImage(client client.APIClient, service *Service, image string) error {
|
||||
fmt.Fprintf(os.Stderr, "Pulling %s (%s)...\n", service.name, image)
|
||||
distributionRef, err := reference.ParseNamed(image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repoInfo, err := registry.ParseRepositoryInfo(distributionRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authConfig := service.context.AuthLookup.Lookup(repoInfo)
|
||||
|
||||
encodedAuth, err := encodeAuthToBase64(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
options := types.ImagePullOptions{
|
||||
RegistryAuth: encodedAuth,
|
||||
}
|
||||
responseBody, err := client.ImagePull(context.Background(), distributionRef.String(), options)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to pull image %s: %v", image, err)
|
||||
return err
|
||||
}
|
||||
defer responseBody.Close()
|
||||
|
||||
var writeBuff io.Writer = os.Stderr
|
||||
|
||||
outFd, isTerminalOut := term.GetFdInfo(os.Stderr)
|
||||
|
||||
err = jsonmessage.DisplayJSONMessagesStream(responseBody, writeBuff, outFd, isTerminalOut, nil)
|
||||
if err != nil {
|
||||
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
||||
// If no error code is set, default to 1
|
||||
if jerr.Code == 0 {
|
||||
jerr.Code = 1
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "%s", writeBuff)
|
||||
return fmt.Errorf("Status: %s, Code: %d", jerr.Message, jerr.Code)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// encodeAuthToBase64 serializes the auth configuration as JSON base64 payload
|
||||
func encodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(buf), nil
|
||||
}
|
||||
92
vendor/github.com/hyperhq/libcompose/docker/name.go
generated
vendored
Normal file
92
vendor/github.com/hyperhq/libcompose/docker/name.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/engine-api/client"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"github.com/hyperhq/libcompose/labels"
|
||||
)
|
||||
|
||||
const format = "%s-%s-%d"
|
||||
|
||||
// Namer defines method to provide container name.
|
||||
type Namer interface {
|
||||
Next() (string, int)
|
||||
}
|
||||
|
||||
type defaultNamer struct {
|
||||
project string
|
||||
service string
|
||||
oneOff bool
|
||||
currentNumber int
|
||||
}
|
||||
|
||||
type singleNamer struct {
|
||||
name string
|
||||
}
|
||||
|
||||
// NewSingleNamer returns a namer that only allows a single name.
|
||||
func NewSingleNamer(name string) Namer {
|
||||
return &singleNamer{name}
|
||||
}
|
||||
|
||||
// NewNamer returns a namer that returns names based on the specified project and
|
||||
// service name and an inner counter, e.g. project_service_1, project_service_2…
|
||||
func NewNamer(client client.APIClient, project, service string, oneOff bool) (Namer, error) {
|
||||
namer := &defaultNamer{
|
||||
project: project,
|
||||
service: service,
|
||||
oneOff: oneOff,
|
||||
}
|
||||
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("label", fmt.Sprintf("%s=%s", labels.PROJECT.Str(), project))
|
||||
filter.Add("label", fmt.Sprintf("%s=%s", labels.SERVICE.Str(), service))
|
||||
if oneOff {
|
||||
filter.Add("label", fmt.Sprintf("%s=%s", labels.ONEOFF.Str(), "True"))
|
||||
} else {
|
||||
filter.Add("label", fmt.Sprintf("%s=%s", labels.ONEOFF.Str(), "False"))
|
||||
}
|
||||
|
||||
containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{
|
||||
All: true,
|
||||
Filter: filter,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
maxNumber := 0
|
||||
for _, container := range containers {
|
||||
number, err := strconv.Atoi(container.Labels[labels.NUMBER.Str()])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if number > maxNumber {
|
||||
maxNumber = number
|
||||
}
|
||||
}
|
||||
namer.currentNumber = maxNumber + 1
|
||||
|
||||
return namer, nil
|
||||
}
|
||||
|
||||
func (i *defaultNamer) Next() (string, int) {
|
||||
service := i.service
|
||||
if i.oneOff {
|
||||
service = i.service + "-run"
|
||||
}
|
||||
name := fmt.Sprintf(format, i.project, service, i.currentNumber)
|
||||
number := i.currentNumber
|
||||
i.currentNumber = i.currentNumber + 1
|
||||
return name, number
|
||||
}
|
||||
|
||||
func (s *singleNamer) Next() (string, int) {
|
||||
return s.name, 1
|
||||
}
|
||||
177
vendor/github.com/hyperhq/libcompose/docker/name_test.go
generated
vendored
Normal file
177
vendor/github.com/hyperhq/libcompose/docker/name_test.go
generated
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/hyperhq/libcompose/labels"
|
||||
"github.com/hyperhq/libcompose/test"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestSingleNamer(t *testing.T) {
|
||||
expectedName := "myName"
|
||||
expectedNumber := 1
|
||||
namer := NewSingleNamer("myName")
|
||||
for i := 0; i < 10; i++ {
|
||||
name, number := namer.Next()
|
||||
if name != expectedName {
|
||||
t.Fatalf("expected %s, got %s", expectedName, name)
|
||||
}
|
||||
if number != expectedNumber {
|
||||
t.Fatalf("expected %d, got %d", expectedNumber, number)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type NamerClient struct {
|
||||
test.NopClient
|
||||
expectedLabelFilters []string
|
||||
containers []types.Container
|
||||
}
|
||||
|
||||
func (client *NamerClient) ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
|
||||
if len(client.expectedLabelFilters) > 1 {
|
||||
labelFilters := options.Filter.Get("label")
|
||||
if len(labelFilters) != len(client.expectedLabelFilters) {
|
||||
return []types.Container{}, fmt.Errorf("expected filters %v, got %v", client.expectedLabelFilters, labelFilters)
|
||||
}
|
||||
for _, expectedLabelFilter := range client.expectedLabelFilters {
|
||||
found := false
|
||||
for _, labelFilter := range labelFilters {
|
||||
if labelFilter == expectedLabelFilter {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return []types.Container{}, fmt.Errorf("expected to find filter %s, did not in %v", expectedLabelFilter, labelFilters)
|
||||
}
|
||||
}
|
||||
}
|
||||
return client.containers, nil
|
||||
}
|
||||
|
||||
func TestDefaultNamerClientError(t *testing.T) {
|
||||
client := test.NewNopClient()
|
||||
_, err := NewNamer(client, "project", "service", false)
|
||||
if err == nil || err.Error() != "Engine no longer exists" {
|
||||
t.Fatalf("expected an error 'Engine no longer exists', got %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultNamerLabelNotANumber(t *testing.T) {
|
||||
client := &NamerClient{
|
||||
containers: []types.Container{
|
||||
{
|
||||
Labels: map[string]string{
|
||||
labels.ONEOFF.Str(): "IAmAString",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := NewNamer(client, "project", "service", false)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error, got nothing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultNamer(t *testing.T) {
|
||||
cases := []struct {
|
||||
projectName string
|
||||
serviceName string
|
||||
oneOff bool
|
||||
containers []types.Container
|
||||
expectedLabels []string
|
||||
expectedName string
|
||||
expectedNumber int
|
||||
}{
|
||||
{
|
||||
projectName: "",
|
||||
serviceName: "",
|
||||
oneOff: false,
|
||||
containers: []types.Container{},
|
||||
expectedLabels: []string{
|
||||
fmt.Sprintf("%s=", labels.PROJECT.Str()),
|
||||
fmt.Sprintf("%s=", labels.SERVICE.Str()),
|
||||
fmt.Sprintf("%s=False", labels.ONEOFF.Str()),
|
||||
},
|
||||
expectedName: "--1",
|
||||
expectedNumber: 1,
|
||||
},
|
||||
{
|
||||
projectName: "project",
|
||||
serviceName: "service",
|
||||
oneOff: false,
|
||||
containers: []types.Container{},
|
||||
expectedLabels: []string{
|
||||
fmt.Sprintf("%s=project", labels.PROJECT.Str()),
|
||||
fmt.Sprintf("%s=service", labels.SERVICE.Str()),
|
||||
fmt.Sprintf("%s=False", labels.ONEOFF.Str()),
|
||||
},
|
||||
expectedName: "project-service-1",
|
||||
expectedNumber: 1,
|
||||
},
|
||||
{
|
||||
projectName: "project",
|
||||
serviceName: "service",
|
||||
oneOff: false,
|
||||
containers: []types.Container{
|
||||
{
|
||||
Labels: map[string]string{
|
||||
labels.NUMBER.Str(): "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedLabels: []string{
|
||||
fmt.Sprintf("%s=project", labels.PROJECT.Str()),
|
||||
fmt.Sprintf("%s=service", labels.SERVICE.Str()),
|
||||
fmt.Sprintf("%s=False", labels.ONEOFF.Str()),
|
||||
},
|
||||
expectedName: "project-service-2",
|
||||
expectedNumber: 2,
|
||||
},
|
||||
{
|
||||
projectName: "project",
|
||||
serviceName: "anotherservice",
|
||||
oneOff: false,
|
||||
containers: []types.Container{
|
||||
{
|
||||
Labels: map[string]string{
|
||||
labels.NUMBER.Str(): "10",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedLabels: []string{
|
||||
fmt.Sprintf("%s=project", labels.PROJECT.Str()),
|
||||
fmt.Sprintf("%s=anotherservice", labels.SERVICE.Str()),
|
||||
fmt.Sprintf("%s=False", labels.ONEOFF.Str()),
|
||||
},
|
||||
expectedName: "project-anotherservice-11",
|
||||
expectedNumber: 11,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
client := &NamerClient{
|
||||
expectedLabelFilters: c.expectedLabels,
|
||||
containers: c.containers,
|
||||
}
|
||||
namer, err := NewNamer(client, c.projectName, c.serviceName, c.oneOff)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
name, number := namer.Next()
|
||||
if name != c.expectedName {
|
||||
t.Errorf("Expected %s, got %s for %v", c.expectedName, name, c)
|
||||
}
|
||||
if number != c.expectedNumber {
|
||||
t.Errorf("Expected %d, got %d for %v", c.expectedNumber, number, c)
|
||||
}
|
||||
_, number = namer.Next()
|
||||
if number != c.expectedNumber+1 {
|
||||
t.Errorf("Expected a 2nd call to increment numbre to %d, got %d for %v", c.expectedNumber+1, number, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
66
vendor/github.com/hyperhq/libcompose/docker/project.go
generated
vendored
Normal file
66
vendor/github.com/hyperhq/libcompose/docker/project.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/lookup"
|
||||
"github.com/hyperhq/libcompose/project"
|
||||
)
|
||||
|
||||
// ComposeVersion is name of docker-compose.yml file syntax supported version
|
||||
const ComposeVersion = "1.5.0"
|
||||
|
||||
// NewProject creates a Project with the specified context.
|
||||
func NewProject(context *Context) (project.APIProject, error) {
|
||||
if context.ResourceLookup == nil {
|
||||
context.ResourceLookup = &lookup.FileConfigLookup{}
|
||||
}
|
||||
|
||||
if context.EnvironmentLookup == nil {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
context.EnvironmentLookup = &lookup.ComposableEnvLookup{
|
||||
Lookups: []config.EnvironmentLookup{
|
||||
&lookup.EnvfileLookup{
|
||||
Path: filepath.Join(cwd, ".env"),
|
||||
},
|
||||
&lookup.OsEnvLookup{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if context.AuthLookup == nil {
|
||||
context.AuthLookup = &ConfigAuthLookup{context}
|
||||
}
|
||||
|
||||
if context.ServiceFactory == nil {
|
||||
context.ServiceFactory = &ServiceFactory{
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
|
||||
if context.ClientFactory == nil {
|
||||
return nil, fmt.Errorf("please provide the client to operate the Hyper.sh")
|
||||
}
|
||||
|
||||
// FIXME(vdemeester) Remove the context duplication ?
|
||||
p := project.NewProject(context.ClientFactory, &context.Context)
|
||||
|
||||
err := p.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = context.open(); err != nil {
|
||||
logrus.Errorf("Failed to open project %s: %v", p.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, err
|
||||
}
|
||||
480
vendor/github.com/hyperhq/libcompose/docker/service.go
generated
vendored
Normal file
480
vendor/github.com/hyperhq/libcompose/docker/service.go
generated
vendored
Normal file
@@ -0,0 +1,480 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/engine-api/client"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/labels"
|
||||
"github.com/hyperhq/libcompose/project"
|
||||
"github.com/hyperhq/libcompose/project/options"
|
||||
"github.com/hyperhq/libcompose/utils"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Service is a project.Service implementations.
|
||||
type Service struct {
|
||||
name string
|
||||
serviceConfig *config.ServiceConfig
|
||||
context *Context
|
||||
}
|
||||
|
||||
// NewService creates a service
|
||||
func NewService(name string, serviceConfig *config.ServiceConfig, context *Context) *Service {
|
||||
return &Service{
|
||||
name: name,
|
||||
serviceConfig: serviceConfig,
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the service name.
|
||||
func (s *Service) Name() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// Config returns the configuration of the service (config.ServiceConfig).
|
||||
func (s *Service) Config() *config.ServiceConfig {
|
||||
return s.serviceConfig
|
||||
}
|
||||
|
||||
// DependentServices returns the dependent services (as an array of ServiceRelationship) of the service.
|
||||
func (s *Service) DependentServices() []project.ServiceRelationship {
|
||||
return project.DefaultDependentServices(s.context.Project, s)
|
||||
}
|
||||
|
||||
// Create implements Service.Create. It ensures the image exists or build it
|
||||
// if it can and then create a container.
|
||||
func (s *Service) Create(options options.Create) error {
|
||||
containers, err := s.collectContainers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imageName, err := s.ensureImageExists(options.NoBuild)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(containers) != 0 {
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
return s.recreateIfNeeded(imageName, c, options.NoRecreate, options.ForceRecreate)
|
||||
})
|
||||
}
|
||||
|
||||
_, err = s.createOne(imageName)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Service) collectContainers() ([]*Container, error) {
|
||||
client := s.context.ClientFactory.Create(s)
|
||||
containers, err := GetContainersByFilter(client, labels.SERVICE.Eq(s.name), labels.PROJECT.Eq(s.context.Project.Name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := []*Container{}
|
||||
|
||||
for _, container := range containers {
|
||||
containerNumber, err := strconv.Atoi(container.Labels[labels.NUMBER.Str()])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Compose add "/" before name, so Name[1] will store actaul name.
|
||||
name := strings.SplitAfter(container.Names[0], "/")
|
||||
result = append(result, NewContainer(client, name[1], containerNumber, s))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) createOne(imageName string) (*Container, error) {
|
||||
containers, err := s.constructContainers(imageName, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return containers[0], err
|
||||
}
|
||||
|
||||
func (s *Service) ensureImageExists(noBuild bool) (string, error) {
|
||||
err := s.imageExists()
|
||||
|
||||
if err == nil {
|
||||
return s.imageName(), nil
|
||||
}
|
||||
|
||||
if err != nil && !client.IsErrImageNotFound(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
/*
|
||||
if s.Config().Build.Context != "" {
|
||||
if noBuild {
|
||||
return "", fmt.Errorf("Service %q needs to be built, but no-build was specified", s.name)
|
||||
}
|
||||
return s.imageName(), s.build(options.Build{})
|
||||
}
|
||||
*/
|
||||
|
||||
return s.imageName(), s.Pull()
|
||||
}
|
||||
|
||||
func (s *Service) imageExists() error {
|
||||
client := s.context.ClientFactory.Create(s)
|
||||
|
||||
_, _, err := client.ImageInspectWithRaw(context.Background(), s.imageName(), false)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Service) imageName() string {
|
||||
if s.Config().Image != "" {
|
||||
return s.Config().Image
|
||||
}
|
||||
return fmt.Sprintf("%s_%s", s.context.ProjectName, s.Name())
|
||||
}
|
||||
|
||||
// Build implements Service.Build. If an imageName is specified or if the context has
|
||||
// no build to work with it will do nothing. Otherwise it will try to build
|
||||
// the image and returns an error if any.
|
||||
func (s *Service) Build(buildOptions options.Build) error {
|
||||
if s.Config().Image != "" {
|
||||
return nil
|
||||
}
|
||||
return s.build(buildOptions)
|
||||
}
|
||||
|
||||
func (s *Service) build(buildOptions options.Build) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) constructContainers(imageName string, count int) ([]*Container, error) {
|
||||
result, err := s.collectContainers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := s.context.ClientFactory.Create(s)
|
||||
|
||||
var namer Namer
|
||||
|
||||
if s.serviceConfig.ContainerName != "" {
|
||||
if count > 1 {
|
||||
logrus.Warnf(`The "%s" service is using the custom container name "%s". Docker requires each container to have a unique name. Remove the custom name to scale the service.`, s.name, s.serviceConfig.ContainerName)
|
||||
}
|
||||
namer = NewSingleNamer(s.serviceConfig.ContainerName)
|
||||
} else {
|
||||
namer, err = NewNamer(client, s.context.Project.Name, s.name, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for i := len(result); i < count; i++ {
|
||||
containerName, containerNumber := namer.Next()
|
||||
|
||||
c := NewContainer(client, containerName, containerNumber, s)
|
||||
|
||||
dockerContainer, err := c.Create(imageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logrus.Debugf("Created container %s: %v", dockerContainer.ID, dockerContainer.Name)
|
||||
|
||||
result = append(result, NewContainer(client, containerName, containerNumber, s))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Up implements Service.Up. It builds the image if needed, creates a container
|
||||
// and start it.
|
||||
func (s *Service) Up(options options.Up) error {
|
||||
containers, err := s.collectContainers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var imageName = s.imageName()
|
||||
if len(containers) == 0 || !options.NoRecreate {
|
||||
imageName, err = s.ensureImageExists(options.NoBuild)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return s.up(imageName, true, options)
|
||||
}
|
||||
|
||||
// Run implements Service.Run. It runs a one of command within the service container.
|
||||
func (s *Service) Run(ctx context.Context, commandParts []string) (int, error) {
|
||||
imageName, err := s.ensureImageExists(false)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
client := s.context.ClientFactory.Create(s)
|
||||
|
||||
namer, err := NewNamer(client, s.context.Project.Name, s.name, true)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
containerName, containerNumber := namer.Next()
|
||||
|
||||
c := NewOneOffContainer(client, containerName, containerNumber, s)
|
||||
|
||||
return c.Run(ctx, imageName, &config.ServiceConfig{Command: commandParts, Tty: true, StdinOpen: true})
|
||||
}
|
||||
|
||||
// Info implements Service.Info. It returns an project.InfoSet with the containers
|
||||
// related to this service (can be multiple if using the scale command).
|
||||
func (s *Service) Info(qFlag bool) (project.InfoSet, error) {
|
||||
result := project.InfoSet{}
|
||||
containers, err := s.collectContainers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, c := range containers {
|
||||
info, err := c.Info(qFlag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, info)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Start implements Service.Start. It tries to start a container without creating it.
|
||||
func (s *Service) Start() error {
|
||||
return s.up("", false, options.Up{})
|
||||
}
|
||||
|
||||
func (s *Service) up(imageName string, create bool, options options.Up) error {
|
||||
containers, err := s.collectContainers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Debugf("Found %d existing containers for service %s", len(containers), s.name)
|
||||
|
||||
if len(containers) == 0 && create {
|
||||
c, err := s.createOne(imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
containers = []*Container{c}
|
||||
}
|
||||
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
if create {
|
||||
if err := s.recreateIfNeeded(imageName, c, options.NoRecreate, options.ForceRecreate); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.Up(imageName)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) recreateIfNeeded(imageName string, c *Container, noRecreate, forceRecreate bool) error {
|
||||
if noRecreate {
|
||||
return nil
|
||||
}
|
||||
outOfSync, err := c.OutOfSync(imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"outOfSync": outOfSync,
|
||||
"ForceRecreate": forceRecreate,
|
||||
"NoRecreate": noRecreate}).Debug("Going to decide if recreate is needed")
|
||||
|
||||
if forceRecreate || outOfSync {
|
||||
logrus.Infof("Recreating %s", s.name)
|
||||
if _, err := c.Recreate(imageName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) eachContainer(action func(*Container) error) error {
|
||||
containers, err := s.collectContainers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tasks := utils.InParallel{}
|
||||
for _, container := range containers {
|
||||
task := func(container *Container) func() error {
|
||||
return func() error {
|
||||
return action(container)
|
||||
}
|
||||
}(container)
|
||||
|
||||
tasks.Add(task)
|
||||
}
|
||||
|
||||
return tasks.Wait()
|
||||
}
|
||||
|
||||
// Stop implements Service.Stop. It stops any containers related to the service.
|
||||
func (s *Service) Stop(timeout int) error {
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
return c.Stop(timeout)
|
||||
})
|
||||
}
|
||||
|
||||
// Restart implements Service.Restart. It restarts any containers related to the service.
|
||||
func (s *Service) Restart(timeout int) error {
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
return c.Restart(timeout)
|
||||
})
|
||||
}
|
||||
|
||||
// Kill implements Service.Kill. It kills any containers related to the service.
|
||||
func (s *Service) Kill(signal string) error {
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
return c.Kill(signal)
|
||||
})
|
||||
}
|
||||
|
||||
// Delete implements Service.Delete. It removes any containers related to the service.
|
||||
func (s *Service) Delete(options options.Delete) error {
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
return c.Delete(options.RemoveVolume)
|
||||
})
|
||||
}
|
||||
|
||||
// Log implements Service.Log. It returns the docker logs for each container related to the service.
|
||||
func (s *Service) Log(follow bool) error {
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
return c.Log(follow)
|
||||
})
|
||||
}
|
||||
|
||||
// Scale implements Service.Scale. It creates or removes containers to have the specified number
|
||||
// of related container to the service to run.
|
||||
func (s *Service) Scale(scale int, timeout int) error {
|
||||
if s.specificiesHostPort() {
|
||||
logrus.Warnf("The \"%s\" service specifies a port on the host. If multiple containers for this service are created on a single host, the port will clash.", s.Name())
|
||||
}
|
||||
|
||||
foundCount := 0
|
||||
err := s.eachContainer(func(c *Container) error {
|
||||
foundCount++
|
||||
if foundCount > scale {
|
||||
err := c.Stop(timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// FIXME(vdemeester) remove volume in scale by default ?
|
||||
return c.Delete(false)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundCount != scale {
|
||||
imageName, err := s.ensureImageExists(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = s.constructContainers(imageName, scale); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return s.up("", false, options.Up{})
|
||||
}
|
||||
|
||||
// Pull implements Service.Pull. It pulls the image of the service and skip the service that
|
||||
// would need to be built.
|
||||
func (s *Service) Pull() error {
|
||||
if s.Config().Image == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return pullImage(s.context.ClientFactory.Create(s), s, s.Config().Image)
|
||||
}
|
||||
|
||||
// Pause implements Service.Pause. It puts into pause the container(s) related
|
||||
// to the service.
|
||||
func (s *Service) Pause() error {
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
return c.Pause()
|
||||
})
|
||||
}
|
||||
|
||||
// Unpause implements Service.Pause. It brings back from pause the container(s)
|
||||
// related to the service.
|
||||
func (s *Service) Unpause() error {
|
||||
return s.eachContainer(func(c *Container) error {
|
||||
return c.Unpause()
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveImage implements Service.RemoveImage. It removes images used for the service
|
||||
// depending on the specified type.
|
||||
func (s *Service) RemoveImage(imageType options.ImageType) error {
|
||||
switch imageType {
|
||||
case "local":
|
||||
if s.Config().Image != "" {
|
||||
return nil
|
||||
}
|
||||
return removeImage(s.context.ClientFactory.Create(s), s.imageName())
|
||||
case "all":
|
||||
return removeImage(s.context.ClientFactory.Create(s), s.imageName())
|
||||
default:
|
||||
// Don't do a thing, should be validated up-front
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Containers implements Service.Containers. It returns the list of containers
|
||||
// that are related to the service.
|
||||
func (s *Service) Containers() ([]project.Container, error) {
|
||||
result := []project.Container{}
|
||||
containers, err := s.collectContainers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, c := range containers {
|
||||
result = append(result, c)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Service) specificiesHostPort() bool {
|
||||
_, bindings, err := nat.ParsePortSpecs(s.Config().Ports)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
for _, portBindings := range bindings {
|
||||
for _, portBinding := range portBindings {
|
||||
if portBinding.HostPort != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
16
vendor/github.com/hyperhq/libcompose/docker/service_factory.go
generated
vendored
Normal file
16
vendor/github.com/hyperhq/libcompose/docker/service_factory.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/hyperhq/libcompose/config"
|
||||
"github.com/hyperhq/libcompose/project"
|
||||
)
|
||||
|
||||
// ServiceFactory is an implementation of project.ServiceFactory.
|
||||
type ServiceFactory struct {
|
||||
context *Context
|
||||
}
|
||||
|
||||
// Create creates a Service based on the specified project, name and service configuration.
|
||||
func (s *ServiceFactory) Create(project *project.Project, name string, serviceConfig *config.ServiceConfig) (project.Service, error) {
|
||||
return NewService(name, serviceConfig, s.context), nil
|
||||
}
|
||||
23
vendor/github.com/hyperhq/libcompose/docker/service_test.go
generated
vendored
Normal file
23
vendor/github.com/hyperhq/libcompose/docker/service_test.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
package docker
|
||||
|
||||
/*
|
||||
func TestSpecifiesHostPort(t *testing.T) {
|
||||
servicesWithHostPort := []Service{
|
||||
{serviceConfig: &config.ServiceConfig{Ports: []string{"8000:8000"}}},
|
||||
{serviceConfig: &config.ServiceConfig{Ports: []string{"127.0.0.1:8000:8000"}}},
|
||||
}
|
||||
|
||||
for _, service := range servicesWithHostPort {
|
||||
assert.True(t, service.specificiesHostPort())
|
||||
}
|
||||
|
||||
servicesWithoutHostPort := []Service{
|
||||
{serviceConfig: &config.ServiceConfig{Ports: []string{"8000"}}},
|
||||
{serviceConfig: &config.ServiceConfig{Ports: []string{"127.0.0.1::8000"}}},
|
||||
}
|
||||
|
||||
for _, service := range servicesWithoutHostPort {
|
||||
assert.False(t, service.specificiesHostPort())
|
||||
}
|
||||
}
|
||||
*/
|
||||
Reference in New Issue
Block a user