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 }