gitlab.com/jfprevost/gitlab-runner-notlscheck@v11.11.4+incompatible/helpers/docker/machine_command.go (about)

     1  package docker_helpers
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/docker/machine/commands/mcndirs"
    18  	"github.com/sirupsen/logrus"
    19  )
    20  
    21  type logWriter struct {
    22  	log    func(args ...interface{})
    23  	reader *bufio.Reader
    24  }
    25  
    26  func (l *logWriter) write(line string) {
    27  	line = strings.TrimRight(line, "\n")
    28  
    29  	if len(line) <= 0 {
    30  		return
    31  	}
    32  
    33  	l.log(line)
    34  }
    35  
    36  func (l *logWriter) watch() {
    37  	for {
    38  		line, err := l.reader.ReadString('\n')
    39  		if err == nil || err == io.EOF {
    40  			l.write(line)
    41  			if err == io.EOF {
    42  				return
    43  			}
    44  		} else {
    45  			if !strings.Contains(err.Error(), "bad file descriptor") {
    46  				logrus.WithError(err).Errorln("Problem while reading command output")
    47  			}
    48  			return
    49  		}
    50  	}
    51  }
    52  
    53  func newLogWriter(logFunction func(args ...interface{}), reader io.Reader) {
    54  	writer := &logWriter{
    55  		log:    logFunction,
    56  		reader: bufio.NewReader(reader),
    57  	}
    58  
    59  	go writer.watch()
    60  }
    61  
    62  func stdoutLogWriter(cmd *exec.Cmd, fields logrus.Fields) {
    63  	log := logrus.WithFields(fields)
    64  	reader, err := cmd.StdoutPipe()
    65  
    66  	if err == nil {
    67  		newLogWriter(log.Infoln, reader)
    68  	}
    69  }
    70  
    71  func stderrLogWriter(cmd *exec.Cmd, fields logrus.Fields) {
    72  	log := logrus.WithFields(fields)
    73  	reader, err := cmd.StderrPipe()
    74  
    75  	if err == nil {
    76  		newLogWriter(log.Errorln, reader)
    77  	}
    78  }
    79  
    80  type machineCommand struct {
    81  	cache     map[string]machineInfo
    82  	cacheLock sync.RWMutex
    83  }
    84  
    85  type machineInfo struct {
    86  	expires time.Time
    87  
    88  	canConnect bool
    89  }
    90  
    91  func (m *machineCommand) Create(driver, name string, opts ...string) error {
    92  	args := []string{
    93  		"create",
    94  		"--driver", driver,
    95  	}
    96  	for _, opt := range opts {
    97  		args = append(args, "--"+opt)
    98  	}
    99  	args = append(args, name)
   100  
   101  	cmd := exec.Command("docker-machine", args...)
   102  	cmd.Env = os.Environ()
   103  
   104  	fields := logrus.Fields{
   105  		"operation": "create",
   106  		"driver":    driver,
   107  		"name":      name,
   108  	}
   109  	stdoutLogWriter(cmd, fields)
   110  	stderrLogWriter(cmd, fields)
   111  
   112  	logrus.Debugln("Executing", cmd.Path, cmd.Args)
   113  	return cmd.Run()
   114  }
   115  
   116  func (m *machineCommand) Provision(name string) error {
   117  	cmd := exec.Command("docker-machine", "provision", name)
   118  	cmd.Env = os.Environ()
   119  
   120  	fields := logrus.Fields{
   121  		"operation": "provision",
   122  		"name":      name,
   123  	}
   124  	stdoutLogWriter(cmd, fields)
   125  	stderrLogWriter(cmd, fields)
   126  
   127  	return cmd.Run()
   128  }
   129  
   130  func (m *machineCommand) Stop(name string, timeout time.Duration) error {
   131  	ctx, ctxCancelFn := context.WithTimeout(context.Background(), timeout)
   132  	defer ctxCancelFn()
   133  
   134  	cmd := exec.CommandContext(ctx, "docker-machine", "stop", name)
   135  	cmd.Env = os.Environ()
   136  
   137  	fields := logrus.Fields{
   138  		"operation": "stop",
   139  		"name":      name,
   140  	}
   141  	stdoutLogWriter(cmd, fields)
   142  	stderrLogWriter(cmd, fields)
   143  
   144  	return cmd.Run()
   145  }
   146  
   147  func (m *machineCommand) Remove(name string) error {
   148  	cmd := exec.Command("docker-machine", "rm", "-y", name)
   149  	cmd.Env = os.Environ()
   150  
   151  	fields := logrus.Fields{
   152  		"operation": "remove",
   153  		"name":      name,
   154  	}
   155  	stdoutLogWriter(cmd, fields)
   156  	stderrLogWriter(cmd, fields)
   157  
   158  	if err := cmd.Run(); err != nil {
   159  		return err
   160  	}
   161  
   162  	m.cacheLock.Lock()
   163  	delete(m.cache, name)
   164  	m.cacheLock.Unlock()
   165  	return nil
   166  }
   167  
   168  func (m *machineCommand) List() (hostNames []string, err error) {
   169  	dir, err := ioutil.ReadDir(mcndirs.GetMachineDir())
   170  	if err != nil {
   171  		if os.IsNotExist(err) {
   172  			return nil, nil
   173  		}
   174  		return nil, err
   175  	}
   176  
   177  	for _, file := range dir {
   178  		if file.IsDir() && !strings.HasPrefix(file.Name(), ".") {
   179  			hostNames = append(hostNames, file.Name())
   180  		}
   181  	}
   182  
   183  	return
   184  }
   185  
   186  func (m *machineCommand) get(args ...string) (out string, err error) {
   187  	// Execute docker-machine to fetch IP
   188  	cmd := exec.Command("docker-machine", args...)
   189  	cmd.Env = os.Environ()
   190  	data, err := cmd.Output()
   191  	if err != nil {
   192  		return
   193  	}
   194  
   195  	// Save the IP
   196  	out = strings.TrimSpace(string(data))
   197  	if out == "" {
   198  		err = fmt.Errorf("failed to get %v", args)
   199  	}
   200  	return
   201  }
   202  
   203  func (m *machineCommand) IP(name string) (string, error) {
   204  	return m.get("ip", name)
   205  }
   206  
   207  func (m *machineCommand) URL(name string) (string, error) {
   208  	return m.get("url", name)
   209  }
   210  
   211  func (m *machineCommand) CertPath(name string) (string, error) {
   212  	return m.get("inspect", name, "-f", "{{.HostOptions.AuthOptions.StorePath}}")
   213  }
   214  
   215  func (m *machineCommand) Status(name string) (string, error) {
   216  	return m.get("status", name)
   217  }
   218  
   219  func (m *machineCommand) Exist(name string) bool {
   220  	configPath := filepath.Join(mcndirs.GetMachineDir(), name, "config.json")
   221  	_, err := os.Stat(configPath)
   222  	if err != nil {
   223  		return false
   224  	}
   225  
   226  	cmd := exec.Command("docker-machine", "inspect", name)
   227  	cmd.Env = os.Environ()
   228  
   229  	fields := logrus.Fields{
   230  		"operation": "exists",
   231  		"name":      name,
   232  	}
   233  	stderrLogWriter(cmd, fields)
   234  
   235  	return cmd.Run() == nil
   236  }
   237  
   238  func (m *machineCommand) CanConnect(name string, skipCache bool) bool {
   239  	m.cacheLock.RLock()
   240  	cachedInfo, ok := m.cache[name]
   241  	m.cacheLock.RUnlock()
   242  
   243  	if ok && !skipCache && time.Now().Before(cachedInfo.expires) {
   244  		return cachedInfo.canConnect
   245  	}
   246  
   247  	canConnect := m.canConnect(name)
   248  	if !canConnect {
   249  		return false // we only cache positive hits. Machines usually do not disconnect.
   250  	}
   251  
   252  	m.cacheLock.Lock()
   253  	m.cache[name] = machineInfo{
   254  		expires:    time.Now().Add(5 * time.Minute),
   255  		canConnect: true,
   256  	}
   257  	m.cacheLock.Unlock()
   258  	return true
   259  }
   260  
   261  func (m *machineCommand) canConnect(name string) bool {
   262  	// Execute docker-machine config which actively ask the machine if it is up and online
   263  	cmd := exec.Command("docker-machine", "config", name)
   264  	cmd.Env = os.Environ()
   265  	err := cmd.Run()
   266  	if err == nil {
   267  		return true
   268  	}
   269  	return false
   270  }
   271  
   272  func (m *machineCommand) Credentials(name string) (dc DockerCredentials, err error) {
   273  	if !m.CanConnect(name, true) {
   274  		err = errors.New("Can't connect")
   275  		return
   276  	}
   277  
   278  	dc.TLSVerify = true
   279  	dc.Host, err = m.URL(name)
   280  	if err == nil {
   281  		dc.CertPath, err = m.CertPath(name)
   282  	}
   283  	return
   284  }
   285  
   286  func NewMachineCommand() Machine {
   287  	return &machineCommand{
   288  		cache: map[string]machineInfo{},
   289  	}
   290  }