github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/worker/container.go (about)

     1  package worker
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  
    11  	"code.cloudfoundry.org/garden"
    12  	"code.cloudfoundry.org/lager"
    13  	"github.com/pf-qiu/concourse/v6/atc/db"
    14  	"github.com/pf-qiu/concourse/v6/atc/runtime"
    15  	"github.com/pf-qiu/concourse/v6/atc/worker/gclient"
    16  )
    17  
    18  var ErrMissingVolume = errors.New("volume mounted to container is missing")
    19  
    20  //go:generate counterfeiter . Container
    21  
    22  type Container interface {
    23  	gclient.Container
    24  	runtime.Runner
    25  
    26  	// TODO: get rid of this, its not used anywhere
    27  	Destroy() error
    28  
    29  	VolumeMounts() []VolumeMount
    30  
    31  	// TODO: get rid of this, its not used anywhere
    32  	WorkerName() string
    33  
    34  	UpdateLastHijack() error
    35  }
    36  
    37  type gardenWorkerContainer struct {
    38  	gclient.Container
    39  	dbContainer db.CreatedContainer
    40  	dbVolumes   []db.CreatedVolume
    41  
    42  	gardenClient gclient.Client
    43  
    44  	volumeMounts []VolumeMount
    45  
    46  	user       string
    47  	workerName string
    48  }
    49  
    50  func newGardenWorkerContainer(
    51  	logger lager.Logger,
    52  	container gclient.Container,
    53  	dbContainer db.CreatedContainer,
    54  	dbContainerVolumes []db.CreatedVolume,
    55  	gardenClient gclient.Client,
    56  	volumeClient VolumeClient,
    57  	workerName string,
    58  ) (Container, error) {
    59  	logger = logger.WithData(
    60  		lager.Data{
    61  			"container": container.Handle(),
    62  			"worker":    workerName,
    63  		},
    64  	)
    65  
    66  	workerContainer := &gardenWorkerContainer{
    67  		Container:   container,
    68  		dbContainer: dbContainer,
    69  		dbVolumes:   dbContainerVolumes,
    70  
    71  		gardenClient: gardenClient,
    72  
    73  		workerName: workerName,
    74  	}
    75  
    76  	err := workerContainer.initializeVolumes(logger, volumeClient)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	properties, err := workerContainer.Properties()
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	if properties["user"] != "" {
    87  		workerContainer.user = properties["user"]
    88  	} else {
    89  		workerContainer.user = "root"
    90  	}
    91  
    92  	return workerContainer, nil
    93  }
    94  
    95  func (container *gardenWorkerContainer) Destroy() error {
    96  	return container.gardenClient.Destroy(container.Handle())
    97  }
    98  
    99  func (container *gardenWorkerContainer) WorkerName() string {
   100  	return container.workerName
   101  }
   102  
   103  func (container *gardenWorkerContainer) UpdateLastHijack() error {
   104  	return container.dbContainer.UpdateLastHijack()
   105  }
   106  
   107  func (container *gardenWorkerContainer) Run(ctx context.Context, spec garden.ProcessSpec, io garden.ProcessIO) (garden.Process, error) {
   108  	spec.User = container.user
   109  	return container.Container.Run(ctx, spec, io)
   110  }
   111  
   112  func (container *gardenWorkerContainer) VolumeMounts() []VolumeMount {
   113  	return container.volumeMounts
   114  }
   115  
   116  func (container *gardenWorkerContainer) initializeVolumes(
   117  	logger lager.Logger,
   118  	volumeClient VolumeClient,
   119  ) error {
   120  
   121  	volumeMounts := []VolumeMount{}
   122  
   123  	for _, dbVolume := range container.dbVolumes {
   124  		volumeLogger := logger.Session("volume", lager.Data{
   125  			"handle": dbVolume.Handle(),
   126  		})
   127  
   128  		volume, volumeFound, err := volumeClient.LookupVolume(logger, dbVolume.Handle())
   129  		if err != nil {
   130  			volumeLogger.Error("failed-to-lookup-volume", err)
   131  			return err
   132  		}
   133  
   134  		if !volumeFound {
   135  			volumeLogger.Error("volume-is-missing-on-worker", ErrMissingVolume, lager.Data{"handle": dbVolume.Handle()})
   136  			return errors.New("volume mounted to container is missing " + dbVolume.Handle() + " from worker " + container.workerName)
   137  		}
   138  
   139  		volumeMounts = append(volumeMounts, VolumeMount{
   140  			Volume:    volume,
   141  			MountPath: dbVolume.Path(),
   142  		})
   143  	}
   144  
   145  	container.volumeMounts = volumeMounts
   146  
   147  	return nil
   148  }
   149  
   150  // TODO (runtime/#4910): this needs to be modified to not be resource specific
   151  // 		the stdout of the run() is expected to be of json format
   152  //      this will break if used with task_step as it does not
   153  //		print out json
   154  func (container *gardenWorkerContainer) RunScript(
   155  	ctx context.Context,
   156  	path string,
   157  	args []string,
   158  	input []byte,
   159  	output interface{},
   160  	logDest io.Writer,
   161  	recoverable bool,
   162  ) error {
   163  	if recoverable {
   164  		result, _ := container.Properties()
   165  		code := result[runtime.ResourceResultPropertyName]
   166  		if code != "" {
   167  			return json.Unmarshal([]byte(code), &output)
   168  		}
   169  	}
   170  
   171  	stdout := new(bytes.Buffer)
   172  	stderr := new(bytes.Buffer)
   173  
   174  	processIO := garden.ProcessIO{
   175  		Stdin:  bytes.NewBuffer(input),
   176  		Stdout: stdout,
   177  	}
   178  
   179  	if logDest != nil {
   180  		processIO.Stderr = logDest
   181  	} else {
   182  		processIO.Stderr = stderr
   183  	}
   184  
   185  	var process garden.Process
   186  	var err error
   187  	if recoverable {
   188  		process, err = container.Attach(ctx, runtime.ResourceProcessID, processIO)
   189  		if err != nil {
   190  			process, err = container.Run(
   191  				ctx,
   192  				garden.ProcessSpec{
   193  					ID:   runtime.ResourceProcessID,
   194  					Path: path,
   195  					Args: args,
   196  				}, processIO)
   197  			if err != nil {
   198  				return err
   199  			}
   200  		}
   201  	} else {
   202  		process, err = container.Run(ctx, garden.ProcessSpec{
   203  			Path: path,
   204  			Args: args,
   205  		}, processIO)
   206  		if err != nil {
   207  			return err
   208  		}
   209  	}
   210  
   211  	processExited := make(chan struct{})
   212  
   213  	var processStatus int
   214  	var processErr error
   215  
   216  	go func() {
   217  		processStatus, processErr = process.Wait()
   218  		close(processExited)
   219  	}()
   220  
   221  	select {
   222  	case <-processExited:
   223  		if processErr != nil {
   224  			return processErr
   225  		}
   226  
   227  		if processStatus != 0 {
   228  			return runtime.ErrResourceScriptFailed{
   229  				Path:       path,
   230  				Args:       args,
   231  				ExitStatus: processStatus,
   232  
   233  				Stderr: stderr.String(),
   234  			}
   235  		}
   236  
   237  		if recoverable {
   238  			err := container.SetProperty(runtime.ResourceResultPropertyName, stdout.String())
   239  			if err != nil {
   240  				return err
   241  			}
   242  		}
   243  
   244  		err := json.Unmarshal(stdout.Bytes(), output)
   245  		if err != nil {
   246  			return fmt.Errorf("%s\n\nwhen parsing resource response:\n\n%s", err, stdout.String())
   247  		}
   248  		return err
   249  
   250  	case <-ctx.Done():
   251  		_ = container.Stop(false)
   252  		<-processExited
   253  		return ctx.Err()
   254  	}
   255  }