2017-03-05 09:56:08 +02:00
|
|
|
package docker
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"io"
|
2019-04-07 14:58:40 +02:00
|
|
|
"os"
|
2017-03-05 09:56:08 +02:00
|
|
|
|
2021-09-26 14:43:14 +02:00
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
"github.com/docker/docker/api/types/network"
|
|
|
|
"github.com/docker/docker/api/types/volume"
|
2021-09-26 21:51:59 +02:00
|
|
|
"github.com/moby/moby/client"
|
|
|
|
"github.com/moby/moby/pkg/jsonmessage"
|
|
|
|
"github.com/moby/moby/pkg/stdcopy"
|
|
|
|
"github.com/moby/term"
|
2021-09-26 14:43:14 +02:00
|
|
|
|
|
|
|
"github.com/woodpecker-ci/woodpecker/pipeline/backend"
|
2017-03-05 09:56:08 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type engine struct {
|
2021-09-26 14:43:14 +02:00
|
|
|
client client.APIClient
|
2017-03-05 09:56:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// New returns a new Docker Engine using the given client.
|
2021-09-26 14:43:14 +02:00
|
|
|
func New(cli client.APIClient) backend.Engine {
|
2017-03-05 09:56:08 +02:00
|
|
|
return &engine{
|
|
|
|
client: cli,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewEnv returns a new Docker Engine using the client connection
|
|
|
|
// environment variables.
|
|
|
|
func NewEnv() (backend.Engine, error) {
|
2021-09-26 21:51:59 +02:00
|
|
|
cli, err := client.NewClientWithOpts(client.FromEnv)
|
2017-03-05 09:56:08 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return New(cli), nil
|
|
|
|
}
|
|
|
|
|
2018-04-01 20:34:01 +02:00
|
|
|
func (e *engine) Setup(_ context.Context, conf *backend.Config) error {
|
2017-03-05 09:56:08 +02:00
|
|
|
for _, vol := range conf.Volumes {
|
2021-09-26 14:43:14 +02:00
|
|
|
_, err := e.client.VolumeCreate(noContext, volume.VolumeCreateBody{
|
2017-03-05 09:56:08 +02:00
|
|
|
Name: vol.Name,
|
|
|
|
Driver: vol.Driver,
|
|
|
|
DriverOpts: vol.DriverOpts,
|
|
|
|
// Labels: defaultLabels,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2021-09-24 16:29:26 +02:00
|
|
|
for _, n := range conf.Networks {
|
|
|
|
_, err := e.client.NetworkCreate(noContext, n.Name, types.NetworkCreate{
|
|
|
|
Driver: n.Driver,
|
|
|
|
Options: n.DriverOpts,
|
2017-03-05 09:56:08 +02:00
|
|
|
// Labels: defaultLabels,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-04-01 20:34:01 +02:00
|
|
|
func (e *engine) Exec(ctx context.Context, proc *backend.Step) error {
|
2017-03-05 09:56:08 +02:00
|
|
|
config := toConfig(proc)
|
|
|
|
hostConfig := toHostConfig(proc)
|
|
|
|
|
|
|
|
// create pull options with encoded authorization credentials.
|
|
|
|
pullopts := types.ImagePullOptions{}
|
|
|
|
if proc.AuthConfig.Username != "" && proc.AuthConfig.Password != "" {
|
|
|
|
pullopts.RegistryAuth, _ = encodeAuthToBase64(proc.AuthConfig)
|
|
|
|
}
|
|
|
|
|
|
|
|
// automatically pull the latest version of the image if requested
|
|
|
|
// by the process configuration.
|
|
|
|
if proc.Pull {
|
2019-04-07 14:58:40 +02:00
|
|
|
responseBody, perr := e.client.ImagePull(ctx, config.Image, pullopts)
|
2017-03-05 09:56:08 +02:00
|
|
|
if perr == nil {
|
2019-04-07 14:58:40 +02:00
|
|
|
defer responseBody.Close()
|
2020-05-18 16:46:13 +02:00
|
|
|
|
2019-04-07 14:58:40 +02:00
|
|
|
fd, isTerminal := term.GetFdInfo(os.Stdout)
|
|
|
|
jsonmessage.DisplayJSONMessagesStream(responseBody, os.Stdout, fd, isTerminal, nil)
|
2017-03-05 09:56:08 +02:00
|
|
|
}
|
|
|
|
// fix for drone/drone#1917
|
|
|
|
if perr != nil && proc.AuthConfig.Password != "" {
|
|
|
|
return perr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-26 14:43:14 +02:00
|
|
|
_, err := e.client.ContainerCreate(ctx, config, hostConfig, nil, nil, proc.Name)
|
|
|
|
if client.IsErrNotFound(err) {
|
2017-03-05 09:56:08 +02:00
|
|
|
// automatically pull and try to re-create the image if the
|
|
|
|
// failure is caused because the image does not exist.
|
2019-04-07 14:58:40 +02:00
|
|
|
responseBody, perr := e.client.ImagePull(ctx, config.Image, pullopts)
|
2017-03-05 09:56:08 +02:00
|
|
|
if perr != nil {
|
|
|
|
return perr
|
|
|
|
}
|
2019-04-07 14:58:40 +02:00
|
|
|
defer responseBody.Close()
|
|
|
|
fd, isTerminal := term.GetFdInfo(os.Stdout)
|
|
|
|
jsonmessage.DisplayJSONMessagesStream(responseBody, os.Stdout, fd, isTerminal, nil)
|
2017-03-05 09:56:08 +02:00
|
|
|
|
2021-09-26 14:43:14 +02:00
|
|
|
_, err = e.client.ContainerCreate(ctx, config, hostConfig, nil, nil, proc.Name)
|
2017-03-05 09:56:08 +02:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-06-03 17:29:02 +02:00
|
|
|
if len(proc.NetworkMode) == 0 {
|
|
|
|
for _, net := range proc.Networks {
|
|
|
|
err = e.client.NetworkConnect(ctx, net.Name, proc.Name, &network.EndpointSettings{
|
|
|
|
Aliases: net.Aliases,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-03-05 09:56:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if proc.Network != "host" { // or bridge, overlay, none, internal, container:<name> ....
|
|
|
|
// err = e.client.NetworkConnect(ctx, proc.Network, proc.Name, &network.EndpointSettings{
|
|
|
|
// Aliases: proc.NetworkAliases,
|
|
|
|
// })
|
|
|
|
// if err != nil {
|
|
|
|
// return err
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
return e.client.ContainerStart(ctx, proc.Name, startOpts)
|
|
|
|
}
|
|
|
|
|
2018-04-01 20:34:01 +02:00
|
|
|
func (e *engine) Kill(_ context.Context, proc *backend.Step) error {
|
2017-03-05 09:56:08 +02:00
|
|
|
return e.client.ContainerKill(noContext, proc.Name, "9")
|
|
|
|
}
|
|
|
|
|
2018-04-01 20:34:01 +02:00
|
|
|
func (e *engine) Wait(ctx context.Context, proc *backend.Step) (*backend.State, error) {
|
2020-05-18 16:46:13 +02:00
|
|
|
wait, errc := e.client.ContainerWait(ctx, proc.Name, "")
|
|
|
|
select {
|
|
|
|
case <-wait:
|
|
|
|
case <-errc:
|
2017-03-05 09:56:08 +02:00
|
|
|
}
|
|
|
|
|
2020-05-18 16:46:13 +02:00
|
|
|
info, err := e.client.ContainerInspect(ctx, proc.Name)
|
2017-03-05 09:56:08 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if info.State.Running {
|
|
|
|
// todo
|
|
|
|
}
|
|
|
|
|
|
|
|
return &backend.State{
|
|
|
|
Exited: true,
|
|
|
|
ExitCode: info.State.ExitCode,
|
|
|
|
OOMKilled: info.State.OOMKilled,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-04-01 20:34:01 +02:00
|
|
|
func (e *engine) Tail(ctx context.Context, proc *backend.Step) (io.ReadCloser, error) {
|
|
|
|
logs, err := e.client.ContainerLogs(ctx, proc.Name, logsOpts)
|
2017-03-05 09:56:08 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
rc, wc := io.Pipe()
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
stdcopy.StdCopy(wc, wc, logs)
|
|
|
|
logs.Close()
|
|
|
|
wc.Close()
|
|
|
|
rc.Close()
|
|
|
|
}()
|
|
|
|
return rc, nil
|
|
|
|
}
|
|
|
|
|
2018-04-01 20:34:01 +02:00
|
|
|
func (e *engine) Destroy(_ context.Context, conf *backend.Config) error {
|
2017-03-05 09:56:08 +02:00
|
|
|
for _, stage := range conf.Stages {
|
|
|
|
for _, step := range stage.Steps {
|
|
|
|
e.client.ContainerKill(noContext, step.Name, "9")
|
|
|
|
e.client.ContainerRemove(noContext, step.Name, removeOpts)
|
|
|
|
}
|
|
|
|
}
|
2021-09-24 16:29:26 +02:00
|
|
|
for _, v := range conf.Volumes {
|
|
|
|
e.client.VolumeRemove(noContext, v.Name, true)
|
2017-03-05 09:56:08 +02:00
|
|
|
}
|
2021-09-24 16:29:26 +02:00
|
|
|
for _, n := range conf.Networks {
|
|
|
|
e.client.NetworkRemove(noContext, n.Name)
|
2017-03-05 09:56:08 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
noContext = context.Background()
|
|
|
|
|
|
|
|
startOpts = types.ContainerStartOptions{}
|
|
|
|
|
|
|
|
removeOpts = types.ContainerRemoveOptions{
|
|
|
|
RemoveVolumes: true,
|
|
|
|
RemoveLinks: false,
|
|
|
|
Force: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
logsOpts = types.ContainerLogsOptions{
|
|
|
|
Follow: true,
|
|
|
|
ShowStdout: true,
|
|
|
|
ShowStderr: true,
|
|
|
|
Details: false,
|
|
|
|
Timestamps: false,
|
|
|
|
}
|
|
|
|
)
|