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

     1  package runner
     2  
     3  import (
     4  	"context"
     5  	"regexp"
     6  	"strings"
     7  
     8  	"github.com/nektos/act/pkg/common"
     9  
    10  	"github.com/sirupsen/logrus"
    11  )
    12  
    13  var commandPatternGA *regexp.Regexp
    14  var commandPatternADO *regexp.Regexp
    15  
    16  func init() {
    17  	commandPatternGA = regexp.MustCompile("^::([^ ]+)( (.+))?::([^\r\n]*)[\r\n]+$")
    18  	commandPatternADO = regexp.MustCompile("^##\\[([^ ]+)( (.+))?]([^\r\n]*)[\r\n]+$")
    19  }
    20  
    21  func tryParseRawActionCommand(line string) (command string, kvPairs map[string]string, arg string, ok bool) {
    22  	if m := commandPatternGA.FindStringSubmatch(line); m != nil {
    23  		command = m[1]
    24  		kvPairs = parseKeyValuePairs(m[3], ",")
    25  		arg = m[4]
    26  		ok = true
    27  	} else if m := commandPatternADO.FindStringSubmatch(line); m != nil {
    28  		command = m[1]
    29  		kvPairs = parseKeyValuePairs(m[3], ";")
    30  		arg = m[4]
    31  		ok = true
    32  	}
    33  	return
    34  }
    35  
    36  func (rc *RunContext) commandHandler(ctx context.Context) common.LineHandler {
    37  	logger := common.Logger(ctx)
    38  	resumeCommand := ""
    39  	return func(line string) bool {
    40  		command, kvPairs, arg, ok := tryParseRawActionCommand(line)
    41  		if !ok {
    42  			return true
    43  		}
    44  
    45  		if resumeCommand != "" && command != resumeCommand {
    46  			logger.WithFields(logrus.Fields{"command": "ignored", "raw": line}).Infof("  \U00002699  %s", line)
    47  			return false
    48  		}
    49  		arg = unescapeCommandData(arg)
    50  		kvPairs = unescapeKvPairs(kvPairs)
    51  		defCommandLogger := logger.WithFields(logrus.Fields{"command": command, "kvPairs": kvPairs, "arg": arg, "raw": line})
    52  		switch command {
    53  		case "set-env":
    54  			rc.setEnv(ctx, kvPairs, arg)
    55  		case "set-output":
    56  			rc.setOutput(ctx, kvPairs, arg)
    57  		case "add-path":
    58  			rc.addPath(ctx, arg)
    59  		case "debug":
    60  			defCommandLogger.Debugf("  \U0001F4AC  %s", line)
    61  		case "warning":
    62  			defCommandLogger.Warnf("  \U0001F6A7  %s", line)
    63  		case "error":
    64  			defCommandLogger.Errorf("  \U00002757  %s", line)
    65  		case "add-mask":
    66  			rc.AddMask(arg)
    67  			defCommandLogger.Infof("  \U00002699  %s", "***")
    68  		case "stop-commands":
    69  			resumeCommand = arg
    70  			defCommandLogger.Infof("  \U00002699  %s", line)
    71  		case resumeCommand:
    72  			resumeCommand = ""
    73  			defCommandLogger.Infof("  \U00002699  %s", line)
    74  		case "save-state":
    75  			defCommandLogger.Infof("  \U0001f4be  %s", line)
    76  			rc.saveState(ctx, kvPairs, arg)
    77  		case "add-matcher":
    78  			defCommandLogger.Infof("  \U00002753 add-matcher %s", arg)
    79  		default:
    80  			defCommandLogger.Infof("  \U00002753  %s", line)
    81  		}
    82  
    83  		return false
    84  	}
    85  }
    86  
    87  func (rc *RunContext) setEnv(ctx context.Context, kvPairs map[string]string, arg string) {
    88  	name := kvPairs["name"]
    89  	common.Logger(ctx).WithFields(logrus.Fields{"command": "set-env", "name": name, "arg": arg}).Infof("  \U00002699  ::set-env:: %s=%s", name, arg)
    90  	if rc.Env == nil {
    91  		rc.Env = make(map[string]string)
    92  	}
    93  	if rc.GlobalEnv == nil {
    94  		rc.GlobalEnv = map[string]string{}
    95  	}
    96  	newenv := map[string]string{
    97  		name: arg,
    98  	}
    99  	mergeIntoMap := mergeIntoMapCaseSensitive
   100  	if rc.JobContainer != nil && rc.JobContainer.IsEnvironmentCaseInsensitive() {
   101  		mergeIntoMap = mergeIntoMapCaseInsensitive
   102  	}
   103  	mergeIntoMap(rc.Env, newenv)
   104  	mergeIntoMap(rc.GlobalEnv, newenv)
   105  }
   106  func (rc *RunContext) setOutput(ctx context.Context, kvPairs map[string]string, arg string) {
   107  	logger := common.Logger(ctx)
   108  	stepID := rc.CurrentStep
   109  	outputName := kvPairs["name"]
   110  	if outputMapping, ok := rc.OutputMappings[MappableOutput{StepID: stepID, OutputName: outputName}]; ok {
   111  		stepID = outputMapping.StepID
   112  		outputName = outputMapping.OutputName
   113  	}
   114  
   115  	result, ok := rc.StepResults[stepID]
   116  	if !ok {
   117  		logger.Infof("  \U00002757  no outputs used step '%s'", stepID)
   118  		return
   119  	}
   120  
   121  	logger.WithFields(logrus.Fields{"command": "set-output", "name": outputName, "arg": arg}).Infof("  \U00002699  ::set-output:: %s=%s", outputName, arg)
   122  	result.Outputs[outputName] = arg
   123  }
   124  func (rc *RunContext) addPath(ctx context.Context, arg string) {
   125  	common.Logger(ctx).WithFields(logrus.Fields{"command": "add-path", "arg": arg}).Infof("  \U00002699  ::add-path:: %s", arg)
   126  	extraPath := []string{arg}
   127  	for _, v := range rc.ExtraPath {
   128  		if v != arg {
   129  			extraPath = append(extraPath, v)
   130  		}
   131  	}
   132  	rc.ExtraPath = extraPath
   133  }
   134  
   135  func parseKeyValuePairs(kvPairs string, separator string) map[string]string {
   136  	rtn := make(map[string]string)
   137  	kvPairList := strings.Split(kvPairs, separator)
   138  	for _, kvPair := range kvPairList {
   139  		kv := strings.Split(kvPair, "=")
   140  		if len(kv) == 2 {
   141  			rtn[kv[0]] = kv[1]
   142  		}
   143  	}
   144  	return rtn
   145  }
   146  func unescapeCommandData(arg string) string {
   147  	escapeMap := map[string]string{
   148  		"%25": "%",
   149  		"%0D": "\r",
   150  		"%0A": "\n",
   151  	}
   152  	for k, v := range escapeMap {
   153  		arg = strings.ReplaceAll(arg, k, v)
   154  	}
   155  	return arg
   156  }
   157  func unescapeCommandProperty(arg string) string {
   158  	escapeMap := map[string]string{
   159  		"%25": "%",
   160  		"%0D": "\r",
   161  		"%0A": "\n",
   162  		"%3A": ":",
   163  		"%2C": ",",
   164  	}
   165  	for k, v := range escapeMap {
   166  		arg = strings.ReplaceAll(arg, k, v)
   167  	}
   168  	return arg
   169  }
   170  func unescapeKvPairs(kvPairs map[string]string) map[string]string {
   171  	for k, v := range kvPairs {
   172  		kvPairs[k] = unescapeCommandProperty(v)
   173  	}
   174  	return kvPairs
   175  }
   176  
   177  func (rc *RunContext) saveState(_ context.Context, kvPairs map[string]string, arg string) {
   178  	stepID := rc.CurrentStep
   179  	if stepID != "" {
   180  		if rc.IntraActionState == nil {
   181  			rc.IntraActionState = map[string]map[string]string{}
   182  		}
   183  		state, ok := rc.IntraActionState[stepID]
   184  		if !ok {
   185  			state = map[string]string{}
   186  			rc.IntraActionState[stepID] = state
   187  		}
   188  		state[kvPairs["name"]] = arg
   189  	}
   190  }