dev-tools/lib/mage/xbuild/docker.go (137 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 xbuild
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"github.com/urso/magetools/clitool"
)
// DockerImage provides based on downloadable docker images.
type DockerImage struct {
Image string
Workdir string
Volumes map[string]string
Env map[string]string
}
type DockerRunner struct {
Image *DockerImage
Keep bool // keep container
Verbose bool
}
// Build pulls the required image.
func (p *DockerImage) Start() error {
cli := clitool.NewCLIExecutor(false)
_, err := cli.Exec(
context.Background(),
clitool.Command{
Path: "docker",
SubCommand: []string{"pull"},
},
clitool.CreateArgs(
clitool.Positional(p.Image),
),
os.Stdout,
os.Stderr,
)
return err
}
func (p *DockerImage) Stop() error {
return nil
}
// Executor creates the execution environment
func (p *DockerImage) Executor(verbose bool) (clitool.Executor, error) {
return &DockerRunner{
Image: p,
Verbose: verbose,
}, nil
}
func (p *DockerImage) Shell() error {
e, _ := p.Executor(false)
_, err := e.Exec(context.Background(),
clitool.Command{Path: "bash"}, clitool.CreateArgs(),
os.Stdout, os.Stderr,
)
return err
}
func (dr *DockerRunner) Exec(
ctx context.Context,
cmd clitool.Command,
args *clitool.Args,
stdout, stderr io.Writer,
) (bool, error) {
var dockerArgs = []clitool.ArgOpt{
clitool.BoolFlag("--rm", !dr.Keep),
clitool.Flag("-i", ""),
clitool.Flag("-t", ""),
clitool.Positional(dr.Image.Image),
}
for k, v := range dr.Image.Env {
dockerArgs = append(dockerArgs, clitool.Flag("-e", fmt.Sprintf("%v=%v", k, v)))
}
for k, v := range dr.Image.Volumes {
dockerArgs = append(dockerArgs, clitool.Flag("-v", fmt.Sprintf("%v:%v", k, v)))
}
w := cmd.WorkingDir
if w == "" {
w = dr.Image.Workdir
}
if w != "" {
dockerArgs = append(dockerArgs, clitool.Flag("-w", w))
}
arguments := args.Build()
if len(cmd.SubCommand) > 0 {
tmp := make([]string, 0, len(arguments)+len(cmd.SubCommand))
tmp = append(tmp, cmd.SubCommand...)
tmp = append(tmp, arguments...)
arguments = tmp
}
runArgs := append([]string{"run"}, clitool.CreateArgs(dockerArgs...).Build()...)
runArgs = append(runArgs, cmd.Path)
runArgs = append(runArgs, arguments...)
osCommand := exec.CommandContext(ctx, "docker", runArgs...)
osCommand.Stdout = stdout
osCommand.Stderr = stderr
osCommand.Stdin = os.Stdin
if dr.Verbose {
fmt.Printf("Exec docker %v\n", runArgs)
}
didRun, exitCode, err := checkError(osCommand.Run())
if err == nil {
return didRun, nil
}
if dr.Verbose {
fmt.Println(" => exit code:", exitCode)
}
if !didRun {
return false, fmt.Errorf("failed to run command: %+v", err)
}
return true, fmt.Errorf("command %v failed with %v: %+v", cmd.Path, exitCode, err)
}
func checkError(err error) (bool, int, error) {
if err == nil {
return true, 0, nil
}
switch e := err.(type) {
case *exec.ExitError:
return e.Exited(), exitStatus(err), err
case interface{ ExitStatus() int }:
return false, exitStatus(err), err
default:
return false, 1, err
}
}
func exitStatus(err error) int {
type exitStatus interface {
ExitStatus() int
}
if err == nil {
return 0
}
switch e := err.(type) {
case exitStatus:
return e.ExitStatus()
case *exec.ExitError:
if sysErr, ok := e.Sys().(exitStatus); ok {
return sysErr.ExitStatus()
}
}
return 1
}