github.com/nektos/act@v0.2.83/pkg/runner/run_context.go (about)

     1  package runner
     2  
     3  import (
     4  	"archive/tar"
     5  	"bufio"
     6  	"bytes"
     7  	"context"
     8  	"crypto/rand"
     9  	"crypto/sha256"
    10  	"encoding/hex"
    11  	"encoding/json"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  	"os"
    16  	"path/filepath"
    17  	"regexp"
    18  	"runtime"
    19  	"strconv"
    20  	"strings"
    21  	"time"
    22  
    23  	"github.com/docker/go-connections/nat"
    24  	"github.com/nektos/act/pkg/common"
    25  	"github.com/nektos/act/pkg/container"
    26  	"github.com/nektos/act/pkg/exprparser"
    27  	"github.com/nektos/act/pkg/model"
    28  	"github.com/opencontainers/selinux/go-selinux"
    29  )
    30  
    31  // RunContext contains info about current job
    32  type RunContext struct {
    33  	Name                string
    34  	Config              *Config
    35  	Matrix              map[string]interface{}
    36  	Run                 *model.Run
    37  	EventJSON           string
    38  	Env                 map[string]string
    39  	GlobalEnv           map[string]string // to pass env changes of GITHUB_ENV and set-env correctly, due to dirty Env field
    40  	ExtraPath           []string
    41  	CurrentStep         string
    42  	StepResults         map[string]*model.StepResult
    43  	IntraActionState    map[string]map[string]string
    44  	ExprEval            ExpressionEvaluator
    45  	JobContainer        container.ExecutionsEnvironment
    46  	ServiceContainers   []container.ExecutionsEnvironment
    47  	OutputMappings      map[MappableOutput]MappableOutput
    48  	JobName             string
    49  	ActionPath          string
    50  	Parent              *RunContext
    51  	Masks               []string
    52  	cleanUpJobContainer common.Executor
    53  	caller              *caller // job calling this RunContext (reusable workflows)
    54  	Cancelled           bool
    55  	nodeToolFullPath    string
    56  }
    57  
    58  func (rc *RunContext) AddMask(mask string) {
    59  	rc.Masks = append(rc.Masks, mask)
    60  }
    61  
    62  type MappableOutput struct {
    63  	StepID     string
    64  	OutputName string
    65  }
    66  
    67  func (rc *RunContext) String() string {
    68  	name := fmt.Sprintf("%s/%s", rc.Run.Workflow.Name, rc.Name)
    69  	if rc.caller != nil {
    70  		// prefix the reusable workflow with the caller job
    71  		// this is required to create unique container names
    72  		name = fmt.Sprintf("%s/%s", rc.caller.runContext.Name, name)
    73  	}
    74  	return name
    75  }
    76  
    77  // GetEnv returns the env for the context
    78  func (rc *RunContext) GetEnv() map[string]string {
    79  	if rc.Env == nil {
    80  		rc.Env = map[string]string{}
    81  		if rc.Run != nil && rc.Run.Workflow != nil && rc.Config != nil {
    82  			job := rc.Run.Job()
    83  			if job != nil {
    84  				rc.Env = mergeMaps(rc.Run.Workflow.Env, job.Environment(), rc.Config.Env)
    85  			}
    86  		}
    87  	}
    88  	rc.Env["ACT"] = "true"
    89  	return rc.Env
    90  }
    91  
    92  func (rc *RunContext) jobContainerName() string {
    93  	return createContainerName("act", rc.String())
    94  }
    95  
    96  // networkName return the name of the network which will be created by `act` automatically for job,
    97  // only create network if using a service container
    98  func (rc *RunContext) networkName() (string, bool) {
    99  	if len(rc.Run.Job().Services) > 0 {
   100  		return fmt.Sprintf("%s-%s-network", rc.jobContainerName(), rc.Run.JobID), true
   101  	}
   102  	if rc.Config.ContainerNetworkMode == "" {
   103  		return "host", false
   104  	}
   105  	return string(rc.Config.ContainerNetworkMode), false
   106  }
   107  
   108  func getDockerDaemonSocketMountPath(daemonPath string) string {
   109  	if protoIndex := strings.Index(daemonPath, "://"); protoIndex != -1 {
   110  		scheme := daemonPath[:protoIndex]
   111  		if strings.EqualFold(scheme, "npipe") {
   112  			// linux container mount on windows, use the default socket path of the VM / wsl2
   113  			return "/var/run/docker.sock"
   114  		} else if strings.EqualFold(scheme, "unix") {
   115  			return daemonPath[protoIndex+3:]
   116  		} else if strings.IndexFunc(scheme, func(r rune) bool {
   117  			return (r < 'a' || r > 'z') && (r < 'A' || r > 'Z')
   118  		}) == -1 {
   119  			// unknown protocol use default
   120  			return "/var/run/docker.sock"
   121  		}
   122  	}
   123  	return daemonPath
   124  }
   125  
   126  // Returns the binds and mounts for the container, resolving paths as appropriate
   127  func (rc *RunContext) GetBindsAndMounts() ([]string, map[string]string) {
   128  	name := rc.jobContainerName()
   129  
   130  	if rc.Config.ContainerDaemonSocket == "" {
   131  		rc.Config.ContainerDaemonSocket = "/var/run/docker.sock"
   132  	}
   133  
   134  	binds := []string{}
   135  	if rc.Config.ContainerDaemonSocket != "-" {
   136  		daemonPath := getDockerDaemonSocketMountPath(rc.Config.ContainerDaemonSocket)
   137  		binds = append(binds, fmt.Sprintf("%s:%s", daemonPath, "/var/run/docker.sock"))
   138  	}
   139  
   140  	ext := container.LinuxContainerEnvironmentExtensions{}
   141  
   142  	if hostEnv, ok := rc.JobContainer.(*container.HostEnvironment); ok {
   143  		mounts := map[string]string{}
   144  		// Permission issues?
   145  		// binds = append(binds, hostEnv.ToolCache+":/opt/hostedtoolcache")
   146  		binds = append(binds, hostEnv.GetActPath()+":"+ext.GetActPath())
   147  		binds = append(binds, hostEnv.ToContainerPath(rc.Config.Workdir)+":"+ext.ToContainerPath(rc.Config.Workdir))
   148  		return binds, mounts
   149  	}
   150  	mounts := map[string]string{
   151  		"act-toolcache": "/opt/hostedtoolcache",
   152  		name + "-env":   ext.GetActPath(),
   153  	}
   154  
   155  	if job := rc.Run.Job(); job != nil {
   156  		if container := job.Container(); container != nil {
   157  			for _, v := range container.Volumes {
   158  				if !strings.Contains(v, ":") || filepath.IsAbs(v) {
   159  					// Bind anonymous volume or host file.
   160  					binds = append(binds, v)
   161  				} else {
   162  					// Mount existing volume.
   163  					paths := strings.SplitN(v, ":", 2)
   164  					mounts[paths[0]] = paths[1]
   165  				}
   166  			}
   167  		}
   168  	}
   169  
   170  	if rc.Config.BindWorkdir {
   171  		bindModifiers := ""
   172  		if runtime.GOOS == "darwin" {
   173  			bindModifiers = ":delegated"
   174  		}
   175  		if selinux.GetEnabled() {
   176  			bindModifiers = ":z"
   177  		}
   178  		binds = append(binds, fmt.Sprintf("%s:%s%s", rc.Config.Workdir, ext.ToContainerPath(rc.Config.Workdir), bindModifiers))
   179  	} else {
   180  		mounts[name] = ext.ToContainerPath(rc.Config.Workdir)
   181  	}
   182  
   183  	return binds, mounts
   184  }
   185  
   186  func (rc *RunContext) startHostEnvironment() common.Executor {
   187  	return func(ctx context.Context) error {
   188  		logger := common.Logger(ctx)
   189  		rawLogger := logger.WithField("raw_output", true)
   190  		logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool {
   191  			if rc.Config.LogOutput {
   192  				rawLogger.Infof("%s", s)
   193  			} else {
   194  				rawLogger.Debugf("%s", s)
   195  			}
   196  			return true
   197  		})
   198  		cacheDir := rc.ActionCacheDir()
   199  		randBytes := make([]byte, 8)
   200  		_, _ = rand.Read(randBytes)
   201  		miscpath := filepath.Join(cacheDir, hex.EncodeToString(randBytes))
   202  		actPath := filepath.Join(miscpath, "act")
   203  		if err := os.MkdirAll(actPath, 0o777); err != nil {
   204  			return err
   205  		}
   206  		path := filepath.Join(miscpath, "hostexecutor")
   207  		if err := os.MkdirAll(path, 0o777); err != nil {
   208  			return err
   209  		}
   210  		runnerTmp := filepath.Join(miscpath, "tmp")
   211  		if err := os.MkdirAll(runnerTmp, 0o777); err != nil {
   212  			return err
   213  		}
   214  		toolCache := filepath.Join(cacheDir, "tool_cache")
   215  		rc.JobContainer = &container.HostEnvironment{
   216  			Path:      path,
   217  			TmpDir:    runnerTmp,
   218  			ToolCache: toolCache,
   219  			Workdir:   rc.Config.Workdir,
   220  			ActPath:   actPath,
   221  			CleanUp: func() {
   222  				os.RemoveAll(miscpath)
   223  			},
   224  			StdOut: logWriter,
   225  		}
   226  		rc.cleanUpJobContainer = rc.JobContainer.Remove()
   227  		for k, v := range rc.JobContainer.GetRunnerContext(ctx) {
   228  			if v, ok := v.(string); ok {
   229  				rc.Env[fmt.Sprintf("RUNNER_%s", strings.ToUpper(k))] = v
   230  			}
   231  		}
   232  		for _, env := range os.Environ() {
   233  			if k, v, ok := strings.Cut(env, "="); ok {
   234  				// don't override
   235  				if _, ok := rc.Env[k]; !ok {
   236  					rc.Env[k] = v
   237  				}
   238  			}
   239  		}
   240  
   241  		return common.NewPipelineExecutor(
   242  			rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{
   243  				Name: "workflow/event.json",
   244  				Mode: 0o644,
   245  				Body: rc.EventJSON,
   246  			}, &container.FileEntry{
   247  				Name: "workflow/envs.txt",
   248  				Mode: 0o666,
   249  				Body: "",
   250  			}),
   251  		)(ctx)
   252  	}
   253  }
   254  
   255  func (rc *RunContext) startJobContainer() common.Executor {
   256  	return func(ctx context.Context) error {
   257  		logger := common.Logger(ctx)
   258  		image := rc.platformImage(ctx)
   259  		rawLogger := logger.WithField("raw_output", true)
   260  		logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool {
   261  			if rc.Config.LogOutput {
   262  				rawLogger.Infof("%s", s)
   263  			} else {
   264  				rawLogger.Debugf("%s", s)
   265  			}
   266  			return true
   267  		})
   268  
   269  		username, password, err := rc.handleCredentials(ctx)
   270  		if err != nil {
   271  			return fmt.Errorf("failed to handle credentials: %s", err)
   272  		}
   273  
   274  		logger.Infof("\U0001f680  Start image=%s", image)
   275  		name := rc.jobContainerName()
   276  
   277  		envList := make([]string, 0)
   278  
   279  		envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_TOOL_CACHE", "/opt/hostedtoolcache"))
   280  		envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_OS", "Linux"))
   281  		envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_ARCH", container.RunnerArch(ctx)))
   282  		envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_TEMP", "/tmp"))
   283  		envList = append(envList, fmt.Sprintf("%s=%s", "LANG", "C.UTF-8")) // Use same locale as GitHub Actions
   284  
   285  		ext := container.LinuxContainerEnvironmentExtensions{}
   286  		binds, mounts := rc.GetBindsAndMounts()
   287  
   288  		// specify the network to which the container will connect when `docker create` stage. (like execute command line: docker create --network <networkName> <image>)
   289  		// if using service containers, will create a new network for the containers.
   290  		// and it will be removed after at last.
   291  		networkName, createAndDeleteNetwork := rc.networkName()
   292  
   293  		// add service containers
   294  		for serviceID, spec := range rc.Run.Job().Services {
   295  			// interpolate env
   296  			interpolatedEnvs := make(map[string]string, len(spec.Env))
   297  			for k, v := range spec.Env {
   298  				interpolatedEnvs[k] = rc.ExprEval.Interpolate(ctx, v)
   299  			}
   300  			envs := make([]string, 0, len(interpolatedEnvs))
   301  			for k, v := range interpolatedEnvs {
   302  				envs = append(envs, fmt.Sprintf("%s=%s", k, v))
   303  			}
   304  			username, password, err = rc.handleServiceCredentials(ctx, spec.Credentials)
   305  			if err != nil {
   306  				return fmt.Errorf("failed to handle service %s credentials: %w", serviceID, err)
   307  			}
   308  
   309  			interpolatedVolumes := make([]string, 0, len(spec.Volumes))
   310  			for _, volume := range spec.Volumes {
   311  				interpolatedVolumes = append(interpolatedVolumes, rc.ExprEval.Interpolate(ctx, volume))
   312  			}
   313  			serviceBinds, serviceMounts := rc.GetServiceBindsAndMounts(interpolatedVolumes)
   314  
   315  			interpolatedPorts := make([]string, 0, len(spec.Ports))
   316  			for _, port := range spec.Ports {
   317  				interpolatedPorts = append(interpolatedPorts, rc.ExprEval.Interpolate(ctx, port))
   318  			}
   319  			exposedPorts, portBindings, err := nat.ParsePortSpecs(interpolatedPorts)
   320  			if err != nil {
   321  				return fmt.Errorf("failed to parse service %s ports: %w", serviceID, err)
   322  			}
   323  
   324  			imageName := rc.ExprEval.Interpolate(ctx, spec.Image)
   325  			if imageName == "" {
   326  				logger.Infof("The service '%s' will not be started because the container definition has an empty image.", serviceID)
   327  				continue
   328  			}
   329  
   330  			serviceContainerName := createContainerName(rc.jobContainerName(), serviceID)
   331  			c := container.NewContainer(&container.NewContainerInput{
   332  				Name:           serviceContainerName,
   333  				WorkingDir:     ext.ToContainerPath(rc.Config.Workdir),
   334  				Image:          imageName,
   335  				Username:       username,
   336  				Password:       password,
   337  				Env:            envs,
   338  				Mounts:         serviceMounts,
   339  				Binds:          serviceBinds,
   340  				Stdout:         logWriter,
   341  				Stderr:         logWriter,
   342  				Privileged:     rc.Config.Privileged,
   343  				UsernsMode:     rc.Config.UsernsMode,
   344  				Platform:       rc.Config.ContainerArchitecture,
   345  				Options:        rc.ExprEval.Interpolate(ctx, spec.Options),
   346  				NetworkMode:    networkName,
   347  				NetworkAliases: []string{serviceID},
   348  				ExposedPorts:   exposedPorts,
   349  				PortBindings:   portBindings,
   350  			})
   351  			rc.ServiceContainers = append(rc.ServiceContainers, c)
   352  		}
   353  
   354  		rc.cleanUpJobContainer = func(ctx context.Context) error {
   355  			reuseJobContainer := func(_ context.Context) bool {
   356  				return rc.Config.ReuseContainers
   357  			}
   358  
   359  			if rc.JobContainer != nil {
   360  				return rc.JobContainer.Remove().IfNot(reuseJobContainer).
   361  					Then(container.NewDockerVolumeRemoveExecutor(rc.jobContainerName(), false)).IfNot(reuseJobContainer).
   362  					Then(container.NewDockerVolumeRemoveExecutor(rc.jobContainerName()+"-env", false)).IfNot(reuseJobContainer).
   363  					Then(func(ctx context.Context) error {
   364  						if len(rc.ServiceContainers) > 0 {
   365  							logger.Infof("Cleaning up services for job %s", rc.JobName)
   366  							if err := rc.stopServiceContainers()(ctx); err != nil {
   367  								logger.Errorf("Error while cleaning services: %v", err)
   368  							}
   369  							if createAndDeleteNetwork {
   370  								// clean network if it has been created by act
   371  								// if using service containers
   372  								// it means that the network to which containers are connecting is created by `act_runner`,
   373  								// so, we should remove the network at last.
   374  								logger.Infof("Cleaning up network for job %s, and network name is: %s", rc.JobName, networkName)
   375  								if err := container.NewDockerNetworkRemoveExecutor(networkName)(ctx); err != nil {
   376  									logger.Errorf("Error while cleaning network: %v", err)
   377  								}
   378  							}
   379  						}
   380  						return nil
   381  					})(ctx)
   382  			}
   383  			return nil
   384  		}
   385  
   386  		jobContainerNetwork := rc.Config.ContainerNetworkMode.NetworkName()
   387  		if rc.containerImage(ctx) != "" {
   388  			jobContainerNetwork = networkName
   389  		} else if jobContainerNetwork == "" {
   390  			jobContainerNetwork = "host"
   391  		}
   392  
   393  		rc.JobContainer = container.NewContainer(&container.NewContainerInput{
   394  			Cmd:            nil,
   395  			Entrypoint:     []string{"tail", "-f", "/dev/null"},
   396  			WorkingDir:     ext.ToContainerPath(rc.Config.Workdir),
   397  			Image:          image,
   398  			Username:       username,
   399  			Password:       password,
   400  			Name:           name,
   401  			Env:            envList,
   402  			Mounts:         mounts,
   403  			NetworkMode:    jobContainerNetwork,
   404  			NetworkAliases: []string{rc.Name},
   405  			Binds:          binds,
   406  			Stdout:         logWriter,
   407  			Stderr:         logWriter,
   408  			Privileged:     rc.Config.Privileged,
   409  			UsernsMode:     rc.Config.UsernsMode,
   410  			Platform:       rc.Config.ContainerArchitecture,
   411  			Options:        rc.options(ctx),
   412  		})
   413  		if rc.JobContainer == nil {
   414  			return errors.New("Failed to create job container")
   415  		}
   416  
   417  		return common.NewPipelineExecutor(
   418  			rc.pullServicesImages(rc.Config.ForcePull),
   419  			rc.JobContainer.Pull(rc.Config.ForcePull),
   420  			rc.stopJobContainer(),
   421  			container.NewDockerNetworkCreateExecutor(networkName).IfBool(createAndDeleteNetwork),
   422  			rc.startServiceContainers(networkName),
   423  			rc.JobContainer.Create(rc.Config.ContainerCapAdd, rc.Config.ContainerCapDrop),
   424  			rc.JobContainer.Start(false),
   425  			rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{
   426  				Name: "workflow/event.json",
   427  				Mode: 0o644,
   428  				Body: rc.EventJSON,
   429  			}, &container.FileEntry{
   430  				Name: "workflow/envs.txt",
   431  				Mode: 0o666,
   432  				Body: "",
   433  			}),
   434  			rc.waitForServiceContainers(),
   435  		)(ctx)
   436  	}
   437  }
   438  
   439  func (rc *RunContext) execJobContainer(cmd []string, env map[string]string, user, workdir string) common.Executor {
   440  	return func(ctx context.Context) error {
   441  		return rc.JobContainer.Exec(cmd, env, user, workdir)(ctx)
   442  	}
   443  }
   444  
   445  func (rc *RunContext) InitializeNodeTool() common.Executor {
   446  	return func(ctx context.Context) error {
   447  		ctx, cancel := common.EarlyCancelContext(ctx)
   448  		defer cancel()
   449  		rc.GetNodeToolFullPath(ctx)
   450  		return nil
   451  	}
   452  }
   453  
   454  func (rc *RunContext) GetNodeToolFullPath(ctx context.Context) string {
   455  	if rc.nodeToolFullPath == "" {
   456  		timeed, cancel := context.WithTimeout(ctx, time.Minute)
   457  		defer cancel()
   458  		path := rc.JobContainer.GetPathVariableName()
   459  		cenv := map[string]string{}
   460  		var cpath string
   461  		if err := rc.JobContainer.UpdateFromImageEnv(&cenv)(ctx); err == nil {
   462  			if p, ok := cenv[path]; ok {
   463  				cpath = p
   464  			}
   465  		}
   466  		if len(cpath) == 0 {
   467  			cpath = rc.JobContainer.DefaultPathVariable()
   468  		}
   469  		cenv[path] = cpath
   470  		hout := &bytes.Buffer{}
   471  		herr := &bytes.Buffer{}
   472  		stdout, stderr := rc.JobContainer.ReplaceLogWriter(hout, herr)
   473  		err := rc.execJobContainer([]string{"node", "--no-warnings", "-e", "console.log(process.execPath)"},
   474  			cenv, "", "").
   475  			Finally(func(context.Context) error {
   476  				rc.JobContainer.ReplaceLogWriter(stdout, stderr)
   477  				return nil
   478  			})(timeed)
   479  		rawStr := strings.Trim(hout.String(), "\r\n")
   480  		if err == nil && !strings.ContainsAny(rawStr, "\r\n") {
   481  			rc.nodeToolFullPath = rawStr
   482  		} else {
   483  			rc.nodeToolFullPath = "node"
   484  		}
   485  	}
   486  	return rc.nodeToolFullPath
   487  }
   488  
   489  func (rc *RunContext) ApplyExtraPath(ctx context.Context, env *map[string]string) {
   490  	if len(rc.ExtraPath) > 0 {
   491  		path := rc.JobContainer.GetPathVariableName()
   492  		if rc.JobContainer.IsEnvironmentCaseInsensitive() {
   493  			// On windows system Path and PATH could also be in the map
   494  			for k := range *env {
   495  				if strings.EqualFold(path, k) {
   496  					path = k
   497  					break
   498  				}
   499  			}
   500  		}
   501  		if (*env)[path] == "" {
   502  			cenv := map[string]string{}
   503  			var cpath string
   504  			if err := rc.JobContainer.UpdateFromImageEnv(&cenv)(ctx); err == nil {
   505  				if p, ok := cenv[path]; ok {
   506  					cpath = p
   507  				}
   508  			}
   509  			if len(cpath) == 0 {
   510  				cpath = rc.JobContainer.DefaultPathVariable()
   511  			}
   512  			(*env)[path] = cpath
   513  		}
   514  		(*env)[path] = rc.JobContainer.JoinPathVariable(append(rc.ExtraPath, (*env)[path])...)
   515  	}
   516  }
   517  
   518  func (rc *RunContext) UpdateExtraPath(ctx context.Context, githubEnvPath string) error {
   519  	if common.Dryrun(ctx) {
   520  		return nil
   521  	}
   522  	pathTar, err := rc.JobContainer.GetContainerArchive(ctx, githubEnvPath)
   523  	if err != nil {
   524  		return err
   525  	}
   526  	defer pathTar.Close()
   527  
   528  	reader := tar.NewReader(pathTar)
   529  	_, err = reader.Next()
   530  	if err != nil && err != io.EOF {
   531  		return err
   532  	}
   533  	s := bufio.NewScanner(reader)
   534  	s.Buffer(nil, 1024*1024*1024) // increase buffer to 1GB to avoid scanner buffer overflow
   535  	firstLine := true
   536  	for s.Scan() {
   537  		line := s.Text()
   538  		if firstLine {
   539  			firstLine = false
   540  			// skip utf8 bom, powershell 5 legacy uses it for utf8
   541  			if len(line) >= 3 && line[0] == 239 && line[1] == 187 && line[2] == 191 {
   542  				line = line[3:]
   543  			}
   544  		}
   545  		if len(line) > 0 {
   546  			rc.addPath(ctx, line)
   547  		}
   548  	}
   549  	return s.Err()
   550  }
   551  
   552  // stopJobContainer removes the job container (if it exists) and its volume (if it exists)
   553  func (rc *RunContext) stopJobContainer() common.Executor {
   554  	return func(ctx context.Context) error {
   555  		if rc.cleanUpJobContainer != nil {
   556  			return rc.cleanUpJobContainer(ctx)
   557  		}
   558  		return nil
   559  	}
   560  }
   561  
   562  func (rc *RunContext) pullServicesImages(forcePull bool) common.Executor {
   563  	return func(ctx context.Context) error {
   564  		execs := []common.Executor{}
   565  		for _, c := range rc.ServiceContainers {
   566  			execs = append(execs, c.Pull(forcePull))
   567  		}
   568  		return common.NewParallelExecutor(len(execs), execs...)(ctx)
   569  	}
   570  }
   571  
   572  func (rc *RunContext) startServiceContainers(_ string) common.Executor {
   573  	return func(ctx context.Context) error {
   574  		execs := []common.Executor{}
   575  		for _, c := range rc.ServiceContainers {
   576  			execs = append(execs, common.NewPipelineExecutor(
   577  				c.Pull(false),
   578  				c.Create(rc.Config.ContainerCapAdd, rc.Config.ContainerCapDrop),
   579  				c.Start(false),
   580  			))
   581  		}
   582  		return common.NewParallelExecutor(len(execs), execs...)(ctx)
   583  	}
   584  }
   585  
   586  func (rc *RunContext) waitForServiceContainer(c container.ExecutionsEnvironment) common.Executor {
   587  	return func(ctx context.Context) error {
   588  		sctx, cancel := context.WithTimeout(ctx, time.Minute*5)
   589  		defer cancel()
   590  		health := container.HealthStarting
   591  		delay := time.Second
   592  		for i := 0; ; i++ {
   593  			health = c.GetHealth(sctx)
   594  			if health != container.HealthStarting || i > 30 {
   595  				break
   596  			}
   597  			time.Sleep(delay)
   598  			delay *= 2
   599  			if delay > 10*time.Second {
   600  				delay = 10 * time.Second
   601  			}
   602  		}
   603  		if health == container.HealthHealthy {
   604  			return nil
   605  		}
   606  		return fmt.Errorf("service container failed to start")
   607  	}
   608  }
   609  
   610  func (rc *RunContext) waitForServiceContainers() common.Executor {
   611  	return func(ctx context.Context) error {
   612  		execs := []common.Executor{}
   613  		for _, c := range rc.ServiceContainers {
   614  			execs = append(execs, rc.waitForServiceContainer(c))
   615  		}
   616  		return common.NewParallelExecutor(len(execs), execs...)(ctx)
   617  	}
   618  }
   619  
   620  func (rc *RunContext) stopServiceContainers() common.Executor {
   621  	return func(ctx context.Context) error {
   622  		execs := []common.Executor{}
   623  		for _, c := range rc.ServiceContainers {
   624  			execs = append(execs, c.Remove().Finally(c.Close()))
   625  		}
   626  		return common.NewParallelExecutor(len(execs), execs...)(ctx)
   627  	}
   628  }
   629  
   630  // Prepare the mounts and binds for the worker
   631  
   632  // ActionCacheDir is for rc
   633  func (rc *RunContext) ActionCacheDir() string {
   634  	if rc.Config.ActionCacheDir != "" {
   635  		return rc.Config.ActionCacheDir
   636  	}
   637  	var xdgCache string
   638  	var ok bool
   639  	if xdgCache, ok = os.LookupEnv("XDG_CACHE_HOME"); !ok || xdgCache == "" {
   640  		if home, err := os.UserHomeDir(); err == nil {
   641  			xdgCache = filepath.Join(home, ".cache")
   642  		} else if xdgCache, err = filepath.Abs("."); err != nil {
   643  			// It's almost impossible to get here, so the temp dir is a good fallback
   644  			xdgCache = os.TempDir()
   645  		}
   646  	}
   647  	return filepath.Join(xdgCache, "act")
   648  }
   649  
   650  // Interpolate outputs after a job is done
   651  func (rc *RunContext) interpolateOutputs() common.Executor {
   652  	return func(ctx context.Context) error {
   653  		ee := rc.NewExpressionEvaluator(ctx)
   654  		for k, v := range rc.Run.Job().Outputs {
   655  			interpolated := ee.Interpolate(ctx, v)
   656  			if v != interpolated {
   657  				rc.Run.Job().Outputs[k] = interpolated
   658  			}
   659  		}
   660  		return nil
   661  	}
   662  }
   663  
   664  func (rc *RunContext) startContainer() common.Executor {
   665  	return func(ctx context.Context) error {
   666  		ctx, cancel := common.EarlyCancelContext(ctx)
   667  		defer cancel()
   668  		if rc.IsHostEnv(ctx) {
   669  			return rc.startHostEnvironment()(ctx)
   670  		}
   671  		return rc.startJobContainer()(ctx)
   672  	}
   673  }
   674  
   675  func (rc *RunContext) IsHostEnv(ctx context.Context) bool {
   676  	platform := rc.runsOnImage(ctx)
   677  	image := rc.containerImage(ctx)
   678  	return image == "" && strings.EqualFold(platform, "-self-hosted")
   679  }
   680  
   681  func (rc *RunContext) stopContainer() common.Executor {
   682  	return rc.stopJobContainer()
   683  }
   684  
   685  func (rc *RunContext) closeContainer() common.Executor {
   686  	return func(ctx context.Context) error {
   687  		if rc.JobContainer != nil {
   688  			return rc.JobContainer.Close()(ctx)
   689  		}
   690  		return nil
   691  	}
   692  }
   693  
   694  func (rc *RunContext) matrix() map[string]interface{} {
   695  	return rc.Matrix
   696  }
   697  
   698  func (rc *RunContext) result(result string) {
   699  	rc.Run.Job().Result = result
   700  }
   701  
   702  func (rc *RunContext) steps() []*model.Step {
   703  	return rc.Run.Job().Steps
   704  }
   705  
   706  // Executor returns a pipeline executor for all the steps in the job
   707  func (rc *RunContext) Executor() (common.Executor, error) {
   708  	var executor common.Executor
   709  	var jobType, err = rc.Run.Job().Type()
   710  
   711  	switch jobType {
   712  	case model.JobTypeDefault:
   713  		executor = newJobExecutor(rc, &stepFactoryImpl{}, rc)
   714  	case model.JobTypeReusableWorkflowLocal:
   715  		executor = newLocalReusableWorkflowExecutor(rc)
   716  	case model.JobTypeReusableWorkflowRemote:
   717  		executor = newRemoteReusableWorkflowExecutor(rc)
   718  	case model.JobTypeInvalid:
   719  		return nil, err
   720  	}
   721  
   722  	return func(ctx context.Context) error {
   723  		res, err := rc.isEnabled(ctx)
   724  		if err != nil {
   725  			return err
   726  		}
   727  		if res {
   728  			return executor(ctx)
   729  		}
   730  		return nil
   731  	}, nil
   732  }
   733  
   734  func (rc *RunContext) containerImage(ctx context.Context) string {
   735  	job := rc.Run.Job()
   736  
   737  	c := job.Container()
   738  	if c != nil {
   739  		return rc.ExprEval.Interpolate(ctx, c.Image)
   740  	}
   741  
   742  	return ""
   743  }
   744  
   745  func (rc *RunContext) runsOnImage(ctx context.Context) string {
   746  	if rc.Run.Job().RunsOn() == nil {
   747  		common.Logger(ctx).Errorf("'runs-on' key not defined in %s", rc.String())
   748  	}
   749  
   750  	for _, platformName := range rc.runsOnPlatformNames(ctx) {
   751  		image := rc.Config.Platforms[strings.ToLower(platformName)]
   752  		if image != "" {
   753  			return image
   754  		}
   755  	}
   756  
   757  	return ""
   758  }
   759  
   760  func (rc *RunContext) runsOnPlatformNames(ctx context.Context) []string {
   761  	job := rc.Run.Job()
   762  
   763  	if job.RunsOn() == nil {
   764  		return []string{}
   765  	}
   766  
   767  	if err := rc.ExprEval.EvaluateYamlNode(ctx, &job.RawRunsOn); err != nil {
   768  		common.Logger(ctx).Errorf("Error while evaluating runs-on: %v", err)
   769  		return []string{}
   770  	}
   771  
   772  	return job.RunsOn()
   773  }
   774  
   775  func (rc *RunContext) platformImage(ctx context.Context) string {
   776  	if containerImage := rc.containerImage(ctx); containerImage != "" {
   777  		return containerImage
   778  	}
   779  
   780  	return rc.runsOnImage(ctx)
   781  }
   782  
   783  func (rc *RunContext) options(ctx context.Context) string {
   784  	job := rc.Run.Job()
   785  	c := job.Container()
   786  	if c != nil {
   787  		return rc.ExprEval.Interpolate(ctx, c.Options)
   788  	}
   789  
   790  	return rc.Config.ContainerOptions
   791  }
   792  
   793  func (rc *RunContext) isEnabled(ctx context.Context) (bool, error) {
   794  	job := rc.Run.Job()
   795  	l := common.Logger(ctx)
   796  	runJob, runJobErr := EvalBool(ctx, rc.ExprEval, job.If.Value, exprparser.DefaultStatusCheckSuccess)
   797  	jobType, jobTypeErr := job.Type()
   798  
   799  	if runJobErr != nil {
   800  		return false, fmt.Errorf("  \u274C  Error in if-expression: \"if: %s\" (%s)", job.If.Value, runJobErr)
   801  	}
   802  
   803  	if jobType == model.JobTypeInvalid {
   804  		return false, jobTypeErr
   805  	}
   806  
   807  	if !runJob {
   808  		rc.result("skipped")
   809  		l.WithField("jobResult", "skipped").Debugf("Skipping job '%s' due to '%s'", job.Name, job.If.Value)
   810  		return false, nil
   811  	}
   812  
   813  	if jobType != model.JobTypeDefault {
   814  		return true, nil
   815  	}
   816  
   817  	img := rc.platformImage(ctx)
   818  	if img == "" {
   819  		for _, platformName := range rc.runsOnPlatformNames(ctx) {
   820  			l.Infof("\U0001F6A7  Skipping unsupported platform -- Try running with `-P %+v=...`", platformName)
   821  		}
   822  		return false, nil
   823  	}
   824  	return true, nil
   825  }
   826  
   827  func mergeMaps(maps ...map[string]string) map[string]string {
   828  	rtnMap := make(map[string]string)
   829  	for _, m := range maps {
   830  		for k, v := range m {
   831  			rtnMap[k] = v
   832  		}
   833  	}
   834  	return rtnMap
   835  }
   836  
   837  func createContainerName(parts ...string) string {
   838  	name := strings.Join(parts, "-")
   839  	pattern := regexp.MustCompile("[^a-zA-Z0-9]")
   840  	name = pattern.ReplaceAllString(name, "-")
   841  	name = strings.ReplaceAll(name, "--", "-")
   842  	hash := sha256.Sum256([]byte(name))
   843  
   844  	// SHA256 is 64 hex characters. So trim name to 63 characters to make room for the hash and separator
   845  	trimmedName := strings.Trim(trimToLen(name, 63), "-")
   846  
   847  	return fmt.Sprintf("%s-%x", trimmedName, hash)
   848  }
   849  
   850  func trimToLen(s string, l int) string {
   851  	if l < 0 {
   852  		l = 0
   853  	}
   854  	if len(s) > l {
   855  		return s[:l]
   856  	}
   857  	return s
   858  }
   859  
   860  func (rc *RunContext) getJobContext() *model.JobContext {
   861  	jobStatus := "success"
   862  	if rc.Cancelled {
   863  		jobStatus = "cancelled"
   864  	} else {
   865  		for _, stepStatus := range rc.StepResults {
   866  			if stepStatus.Conclusion == model.StepStatusFailure {
   867  				jobStatus = "failure"
   868  				break
   869  			}
   870  		}
   871  	}
   872  	return &model.JobContext{
   873  		Status: jobStatus,
   874  	}
   875  }
   876  
   877  func (rc *RunContext) getStepsContext() map[string]*model.StepResult {
   878  	return rc.StepResults
   879  }
   880  
   881  func (rc *RunContext) getGithubContext(ctx context.Context) *model.GithubContext {
   882  	logger := common.Logger(ctx)
   883  	ghc := &model.GithubContext{
   884  		Event:            make(map[string]interface{}),
   885  		Workflow:         rc.Run.Workflow.Name,
   886  		RunAttempt:       rc.Config.Env["GITHUB_RUN_ATTEMPT"],
   887  		RunID:            rc.Config.Env["GITHUB_RUN_ID"],
   888  		RunNumber:        rc.Config.Env["GITHUB_RUN_NUMBER"],
   889  		Actor:            rc.Config.Actor,
   890  		EventName:        rc.Config.EventName,
   891  		Action:           rc.CurrentStep,
   892  		Token:            rc.Config.Token,
   893  		Job:              rc.Run.JobID,
   894  		ActionPath:       rc.ActionPath,
   895  		ActionRepository: rc.Env["GITHUB_ACTION_REPOSITORY"],
   896  		ActionRef:        rc.Env["GITHUB_ACTION_REF"],
   897  		RepositoryOwner:  rc.Config.Env["GITHUB_REPOSITORY_OWNER"],
   898  		RetentionDays:    rc.Config.Env["GITHUB_RETENTION_DAYS"],
   899  		RunnerPerflog:    rc.Config.Env["RUNNER_PERFLOG"],
   900  		RunnerTrackingID: rc.Config.Env["RUNNER_TRACKING_ID"],
   901  		Repository:       rc.Config.Env["GITHUB_REPOSITORY"],
   902  		Ref:              rc.Config.Env["GITHUB_REF"],
   903  		Sha:              rc.Config.Env["SHA_REF"],
   904  		RefName:          rc.Config.Env["GITHUB_REF_NAME"],
   905  		RefType:          rc.Config.Env["GITHUB_REF_TYPE"],
   906  		BaseRef:          rc.Config.Env["GITHUB_BASE_REF"],
   907  		HeadRef:          rc.Config.Env["GITHUB_HEAD_REF"],
   908  		Workspace:        rc.Config.Env["GITHUB_WORKSPACE"],
   909  	}
   910  	if rc.JobContainer != nil {
   911  		ghc.EventPath = rc.JobContainer.GetActPath() + "/workflow/event.json"
   912  		ghc.Workspace = rc.JobContainer.ToContainerPath(rc.Config.Workdir)
   913  	}
   914  
   915  	if ghc.RunAttempt == "" {
   916  		ghc.RunAttempt = "1"
   917  	}
   918  
   919  	if ghc.RunID == "" {
   920  		ghc.RunID = "1"
   921  	}
   922  
   923  	if ghc.RunNumber == "" {
   924  		ghc.RunNumber = "1"
   925  	}
   926  
   927  	if ghc.RetentionDays == "" {
   928  		ghc.RetentionDays = "0"
   929  	}
   930  
   931  	if ghc.RunnerPerflog == "" {
   932  		ghc.RunnerPerflog = "/dev/null"
   933  	}
   934  
   935  	// Backwards compatibility for configs that require
   936  	// a default rather than being run as a cmd
   937  	if ghc.Actor == "" {
   938  		ghc.Actor = "nektos/act"
   939  	}
   940  
   941  	if rc.EventJSON != "" {
   942  		err := json.Unmarshal([]byte(rc.EventJSON), &ghc.Event)
   943  		if err != nil {
   944  			logger.Errorf("Unable to Unmarshal event '%s': %v", rc.EventJSON, err)
   945  		}
   946  	}
   947  
   948  	ghc.SetBaseAndHeadRef()
   949  	repoPath := rc.Config.Workdir
   950  	ghc.SetRepositoryAndOwner(ctx, rc.Config.GitHubInstance, rc.Config.RemoteName, repoPath)
   951  	if ghc.Ref == "" {
   952  		ghc.SetRef(ctx, rc.Config.DefaultBranch, repoPath)
   953  	}
   954  	if ghc.Sha == "" {
   955  		ghc.SetSha(ctx, repoPath)
   956  	}
   957  
   958  	ghc.SetRefTypeAndName()
   959  
   960  	// defaults
   961  	ghc.ServerURL = "https://github.com"
   962  	ghc.APIURL = "https://api.github.com"
   963  	ghc.GraphQLURL = "https://api.github.com/graphql"
   964  	// per GHES
   965  	if rc.Config.GitHubInstance != "github.com" {
   966  		ghc.ServerURL = fmt.Sprintf("https://%s", rc.Config.GitHubInstance)
   967  		ghc.APIURL = fmt.Sprintf("https://%s/api/v3", rc.Config.GitHubInstance)
   968  		ghc.GraphQLURL = fmt.Sprintf("https://%s/api/graphql", rc.Config.GitHubInstance)
   969  	}
   970  	// allow to be overridden by user
   971  	if rc.Config.Env["GITHUB_SERVER_URL"] != "" {
   972  		ghc.ServerURL = rc.Config.Env["GITHUB_SERVER_URL"]
   973  	}
   974  	if rc.Config.Env["GITHUB_API_URL"] != "" {
   975  		ghc.APIURL = rc.Config.Env["GITHUB_API_URL"]
   976  	}
   977  	if rc.Config.Env["GITHUB_GRAPHQL_URL"] != "" {
   978  		ghc.GraphQLURL = rc.Config.Env["GITHUB_GRAPHQL_URL"]
   979  	}
   980  
   981  	return ghc
   982  }
   983  
   984  func isLocalCheckout(ghc *model.GithubContext, step *model.Step) bool {
   985  	if step.Type() == model.StepTypeInvalid {
   986  		// This will be errored out by the executor later, we need this here to avoid a null panic though
   987  		return false
   988  	}
   989  	if step.Type() != model.StepTypeUsesActionRemote {
   990  		return false
   991  	}
   992  	remoteAction := newRemoteAction(step.Uses)
   993  	if remoteAction == nil {
   994  		// IsCheckout() will nil panic if we dont bail out early
   995  		return false
   996  	}
   997  	if !remoteAction.IsCheckout() {
   998  		return false
   999  	}
  1000  
  1001  	if repository, ok := step.With["repository"]; ok && repository != ghc.Repository {
  1002  		return false
  1003  	}
  1004  	if repository, ok := step.With["ref"]; ok && repository != ghc.Ref {
  1005  		return false
  1006  	}
  1007  	return true
  1008  }
  1009  
  1010  func nestedMapLookup(m map[string]interface{}, ks ...string) (rval interface{}) {
  1011  	var ok bool
  1012  
  1013  	if len(ks) == 0 { // degenerate input
  1014  		return nil
  1015  	}
  1016  	if rval, ok = m[ks[0]]; !ok {
  1017  		return nil
  1018  	} else if len(ks) == 1 { // we've reached the final key
  1019  		return rval
  1020  	} else if m, ok = rval.(map[string]interface{}); !ok {
  1021  		return nil
  1022  	}
  1023  	// 1+ more keys
  1024  	return nestedMapLookup(m, ks[1:]...)
  1025  }
  1026  
  1027  func (rc *RunContext) withGithubEnv(ctx context.Context, github *model.GithubContext, env map[string]string) map[string]string {
  1028  	env["CI"] = "true"
  1029  	env["GITHUB_WORKFLOW"] = github.Workflow
  1030  	env["GITHUB_RUN_ATTEMPT"] = github.RunAttempt
  1031  	env["GITHUB_RUN_ID"] = github.RunID
  1032  	env["GITHUB_RUN_NUMBER"] = github.RunNumber
  1033  	env["GITHUB_ACTION"] = github.Action
  1034  	env["GITHUB_ACTION_PATH"] = github.ActionPath
  1035  	env["GITHUB_ACTION_REPOSITORY"] = github.ActionRepository
  1036  	env["GITHUB_ACTION_REF"] = github.ActionRef
  1037  	env["GITHUB_ACTIONS"] = "true"
  1038  	env["GITHUB_ACTOR"] = github.Actor
  1039  	env["GITHUB_REPOSITORY"] = github.Repository
  1040  	env["GITHUB_EVENT_NAME"] = github.EventName
  1041  	env["GITHUB_EVENT_PATH"] = github.EventPath
  1042  	env["GITHUB_WORKSPACE"] = github.Workspace
  1043  	env["GITHUB_SHA"] = github.Sha
  1044  	env["GITHUB_REF"] = github.Ref
  1045  	env["GITHUB_REF_NAME"] = github.RefName
  1046  	env["GITHUB_REF_TYPE"] = github.RefType
  1047  	env["GITHUB_JOB"] = github.Job
  1048  	env["GITHUB_REPOSITORY_OWNER"] = github.RepositoryOwner
  1049  	env["GITHUB_RETENTION_DAYS"] = github.RetentionDays
  1050  	env["RUNNER_PERFLOG"] = github.RunnerPerflog
  1051  	env["RUNNER_TRACKING_ID"] = github.RunnerTrackingID
  1052  	env["GITHUB_BASE_REF"] = github.BaseRef
  1053  	env["GITHUB_HEAD_REF"] = github.HeadRef
  1054  	env["GITHUB_SERVER_URL"] = github.ServerURL
  1055  	env["GITHUB_API_URL"] = github.APIURL
  1056  	env["GITHUB_GRAPHQL_URL"] = github.GraphQLURL
  1057  
  1058  	if rc.Config.ArtifactServerPath != "" {
  1059  		setActionRuntimeVars(rc, env)
  1060  	}
  1061  
  1062  	for _, platformName := range rc.runsOnPlatformNames(ctx) {
  1063  		if platformName != "" {
  1064  			if platformName == "ubuntu-latest" {
  1065  				// hardcode current ubuntu-latest since we have no way to check that 'on the fly'
  1066  				env["ImageOS"] = "ubuntu20"
  1067  			} else {
  1068  				platformName = strings.SplitN(strings.Replace(platformName, `-`, ``, 1), `.`, 2)[0]
  1069  				env["ImageOS"] = platformName
  1070  			}
  1071  		}
  1072  	}
  1073  
  1074  	return env
  1075  }
  1076  
  1077  func setActionRuntimeVars(rc *RunContext, env map[string]string) {
  1078  	actionsRuntimeURL := os.Getenv("ACTIONS_RUNTIME_URL")
  1079  	if actionsRuntimeURL == "" {
  1080  		actionsRuntimeURL = fmt.Sprintf("http://%s:%s/", rc.Config.ArtifactServerAddr, rc.Config.ArtifactServerPort)
  1081  	}
  1082  	env["ACTIONS_RUNTIME_URL"] = actionsRuntimeURL
  1083  	env["ACTIONS_RESULTS_URL"] = actionsRuntimeURL
  1084  
  1085  	actionsRuntimeToken := os.Getenv("ACTIONS_RUNTIME_TOKEN")
  1086  	if actionsRuntimeToken == "" {
  1087  		runID := int64(1)
  1088  		if rid, ok := rc.Config.Env["GITHUB_RUN_ID"]; ok {
  1089  			runID, _ = strconv.ParseInt(rid, 10, 64)
  1090  		}
  1091  		actionsRuntimeToken, _ = common.CreateAuthorizationToken(runID, runID, runID)
  1092  	}
  1093  	env["ACTIONS_RUNTIME_TOKEN"] = actionsRuntimeToken
  1094  }
  1095  
  1096  func (rc *RunContext) handleCredentials(ctx context.Context) (string, string, error) {
  1097  	// TODO: remove below 2 lines when we can release act with breaking changes
  1098  	username := rc.Config.Secrets["DOCKER_USERNAME"]
  1099  	password := rc.Config.Secrets["DOCKER_PASSWORD"]
  1100  
  1101  	container := rc.Run.Job().Container()
  1102  	if container == nil || container.Credentials == nil {
  1103  		return username, password, nil
  1104  	}
  1105  
  1106  	if container.Credentials != nil && len(container.Credentials) != 2 {
  1107  		err := fmt.Errorf("invalid property count for key 'credentials:'")
  1108  		return "", "", err
  1109  	}
  1110  
  1111  	ee := rc.NewExpressionEvaluator(ctx)
  1112  	if username = ee.Interpolate(ctx, container.Credentials["username"]); username == "" {
  1113  		err := fmt.Errorf("failed to interpolate container.credentials.username")
  1114  		return "", "", err
  1115  	}
  1116  	if password = ee.Interpolate(ctx, container.Credentials["password"]); password == "" {
  1117  		err := fmt.Errorf("failed to interpolate container.credentials.password")
  1118  		return "", "", err
  1119  	}
  1120  
  1121  	if container.Credentials["username"] == "" || container.Credentials["password"] == "" {
  1122  		err := fmt.Errorf("container.credentials cannot be empty")
  1123  		return "", "", err
  1124  	}
  1125  
  1126  	return username, password, nil
  1127  }
  1128  
  1129  func (rc *RunContext) handleServiceCredentials(ctx context.Context, creds map[string]string) (username, password string, err error) {
  1130  	if creds == nil {
  1131  		return
  1132  	}
  1133  	if len(creds) != 2 {
  1134  		err = fmt.Errorf("invalid property count for key 'credentials:'")
  1135  		return
  1136  	}
  1137  
  1138  	ee := rc.NewExpressionEvaluator(ctx)
  1139  	if username = ee.Interpolate(ctx, creds["username"]); username == "" {
  1140  		err = fmt.Errorf("failed to interpolate credentials.username")
  1141  		return
  1142  	}
  1143  
  1144  	if password = ee.Interpolate(ctx, creds["password"]); password == "" {
  1145  		err = fmt.Errorf("failed to interpolate credentials.password")
  1146  		return
  1147  	}
  1148  
  1149  	return
  1150  }
  1151  
  1152  // GetServiceBindsAndMounts returns the binds and mounts for the service container, resolving paths as appropriate
  1153  func (rc *RunContext) GetServiceBindsAndMounts(svcVolumes []string) ([]string, map[string]string) {
  1154  	if rc.Config.ContainerDaemonSocket == "" {
  1155  		rc.Config.ContainerDaemonSocket = "/var/run/docker.sock"
  1156  	}
  1157  	binds := []string{}
  1158  	if rc.Config.ContainerDaemonSocket != "-" {
  1159  		daemonPath := getDockerDaemonSocketMountPath(rc.Config.ContainerDaemonSocket)
  1160  		binds = append(binds, fmt.Sprintf("%s:%s", daemonPath, "/var/run/docker.sock"))
  1161  	}
  1162  
  1163  	mounts := map[string]string{}
  1164  
  1165  	for _, v := range svcVolumes {
  1166  		if !strings.Contains(v, ":") || filepath.IsAbs(v) {
  1167  			// Bind anonymous volume or host file.
  1168  			binds = append(binds, v)
  1169  		} else {
  1170  			// Mount existing volume.
  1171  			paths := strings.SplitN(v, ":", 2)
  1172  			mounts[paths[0]] = paths[1]
  1173  		}
  1174  	}
  1175  
  1176  	return binds, mounts
  1177  }