github.com/secure-build/gitlab-runner@v12.5.0+incompatible/executors/custom/executor.go (about) 1 package custom 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "path/filepath" 11 12 "github.com/sirupsen/logrus" 13 14 "gitlab.com/gitlab-org/gitlab-runner/common" 15 "gitlab.com/gitlab-org/gitlab-runner/executors" 16 "gitlab.com/gitlab-org/gitlab-runner/executors/custom/api" 17 "gitlab.com/gitlab-org/gitlab-runner/executors/custom/command" 18 ) 19 20 type commandOutputs struct { 21 stdout io.Writer 22 stderr io.Writer 23 } 24 25 type prepareCommandOpts struct { 26 executable string 27 args []string 28 out commandOutputs 29 } 30 31 type ConfigExecOutput struct { 32 api.ConfigExecOutput 33 } 34 35 func (c *ConfigExecOutput) InjectInto(executor *executor) { 36 if c.Hostname != nil { 37 executor.Build.Hostname = *c.Hostname 38 } 39 40 if c.BuildsDir != nil { 41 executor.Config.BuildsDir = *c.BuildsDir 42 } 43 44 if c.CacheDir != nil { 45 executor.Config.CacheDir = *c.CacheDir 46 } 47 48 if c.BuildsDirIsShared != nil { 49 executor.SharedBuildsDir = *c.BuildsDirIsShared 50 } 51 52 executor.driverInfo = c.Driver 53 } 54 55 type executor struct { 56 executors.AbstractExecutor 57 58 config *config 59 tempDir string 60 61 driverInfo *api.DriverInfo 62 } 63 64 func (e *executor) Prepare(options common.ExecutorPrepareOptions) error { 65 e.AbstractExecutor.PrepareConfiguration(options) 66 67 err := e.prepareConfig() 68 if err != nil { 69 return err 70 } 71 72 e.tempDir, err = ioutil.TempDir("", "custom-executor") 73 if err != nil { 74 return err 75 } 76 77 err = e.dynamicConfig() 78 if err != nil { 79 return err 80 } 81 82 e.logStartupMessage() 83 84 err = e.AbstractExecutor.PrepareBuildAndShell() 85 if err != nil { 86 return err 87 } 88 89 // nothing to do, as there's no prepare_script 90 if e.config.PrepareExec == "" { 91 return nil 92 } 93 94 ctx, cancelFunc := context.WithTimeout(e.Context, e.config.GetPrepareExecTimeout()) 95 defer cancelFunc() 96 97 opts := prepareCommandOpts{ 98 executable: e.config.PrepareExec, 99 args: e.config.PrepareArgs, 100 out: e.defaultCommandOutputs(), 101 } 102 103 return e.prepareCommand(ctx, opts).Run() 104 } 105 106 func (e *executor) prepareConfig() error { 107 if e.Config.Custom == nil { 108 return common.MakeBuildError("custom executor not configured") 109 } 110 111 e.config = &config{ 112 CustomConfig: e.Config.Custom, 113 } 114 115 if e.config.RunExec == "" { 116 return common.MakeBuildError("custom executor is missing RunExec") 117 } 118 119 return nil 120 } 121 122 func (e *executor) dynamicConfig() error { 123 if e.config.ConfigExec == "" { 124 return nil 125 } 126 127 ctx, cancelFunc := context.WithTimeout(e.Context, e.config.GetConfigExecTimeout()) 128 defer cancelFunc() 129 130 buf := bytes.NewBuffer(nil) 131 outputs := commandOutputs{ 132 stdout: buf, 133 stderr: e.Trace, 134 } 135 136 opts := prepareCommandOpts{ 137 executable: e.config.ConfigExec, 138 args: e.config.ConfigArgs, 139 out: outputs, 140 } 141 142 err := e.prepareCommand(ctx, opts).Run() 143 if err != nil { 144 return err 145 } 146 147 jsonConfig := buf.Bytes() 148 if len(jsonConfig) < 1 { 149 return nil 150 } 151 152 config := new(ConfigExecOutput) 153 154 err = json.Unmarshal(jsonConfig, config) 155 if err != nil { 156 return fmt.Errorf("error while parsing JSON output: %v", err) 157 } 158 159 config.InjectInto(e) 160 161 return nil 162 } 163 164 func (e *executor) logStartupMessage() { 165 const usageLine = "Using Custom executor" 166 167 info := e.driverInfo 168 if info == nil || info.Name == nil { 169 e.Println(fmt.Sprintf("%s...", usageLine)) 170 return 171 } 172 173 if info.Version == nil { 174 e.Println(fmt.Sprintf("%s with driver %s...", usageLine, *info.Name)) 175 return 176 } 177 178 e.Println(fmt.Sprintf("%s with driver %s %s...", usageLine, *info.Name, *info.Version)) 179 } 180 181 func (e *executor) defaultCommandOutputs() commandOutputs { 182 return commandOutputs{ 183 stdout: e.Trace, 184 stderr: e.Trace, 185 } 186 } 187 188 var commandFactory = command.New 189 190 func (e *executor) prepareCommand(ctx context.Context, opts prepareCommandOpts) command.Command { 191 cmdOpts := command.CreateOptions{ 192 Dir: e.tempDir, 193 Env: make([]string, 0), 194 Stdout: opts.out.stdout, 195 Stderr: opts.out.stderr, 196 Logger: e.BuildLogger, 197 GracefulKillTimeout: e.config.GetGracefulKillTimeout(), 198 ForceKillTimeout: e.config.GetForceKillTimeout(), 199 } 200 201 for _, variable := range e.Build.GetAllVariables() { 202 cmdOpts.Env = append(cmdOpts.Env, fmt.Sprintf("CUSTOM_ENV_%s=%s", variable.Key, variable.Value)) 203 } 204 205 return commandFactory(ctx, opts.executable, opts.args, cmdOpts) 206 } 207 208 func (e *executor) Run(cmd common.ExecutorCommand) error { 209 scriptDir, err := ioutil.TempDir(e.tempDir, "script") 210 if err != nil { 211 return err 212 } 213 214 scriptFile := filepath.Join(scriptDir, "script."+e.BuildShell.Extension) 215 err = ioutil.WriteFile(scriptFile, []byte(cmd.Script), 0700) 216 if err != nil { 217 return err 218 } 219 220 args := append(e.config.RunArgs, scriptFile, string(cmd.Stage)) 221 222 opts := prepareCommandOpts{ 223 executable: e.config.RunExec, 224 args: args, 225 out: e.defaultCommandOutputs(), 226 } 227 228 return e.prepareCommand(cmd.Context, opts).Run() 229 } 230 231 func (e *executor) Cleanup() { 232 e.AbstractExecutor.Cleanup() 233 234 err := e.prepareConfig() 235 if err != nil { 236 e.Warningln(err) 237 238 // at this moment we don't care about the errors 239 return 240 } 241 242 // nothing to do, as there's no cleanup_script 243 if e.config.CleanupExec == "" { 244 return 245 } 246 247 ctx, cancelFunc := context.WithTimeout(context.Background(), e.config.GetCleanupScriptTimeout()) 248 defer cancelFunc() 249 250 stdoutLogger := e.BuildLogger.WithFields(logrus.Fields{"cleanup_std": "out"}) 251 stderrLogger := e.BuildLogger.WithFields(logrus.Fields{"cleanup_std": "err"}) 252 253 outputs := commandOutputs{ 254 stdout: stdoutLogger.WriterLevel(logrus.DebugLevel), 255 stderr: stderrLogger.WriterLevel(logrus.WarnLevel), 256 } 257 258 opts := prepareCommandOpts{ 259 executable: e.config.CleanupExec, 260 args: e.config.CleanupArgs, 261 out: outputs, 262 } 263 264 err = e.prepareCommand(ctx, opts).Run() 265 if err != nil { 266 e.Warningln("Cleanup script failed:", err) 267 } 268 } 269 270 func init() { 271 options := executors.ExecutorOptions{ 272 DefaultCustomBuildsDirEnabled: false, 273 Shell: common.ShellScriptInfo{ 274 Shell: common.GetDefaultShell(), 275 Type: common.NormalShell, 276 RunnerCommand: "gitlab-runner", 277 }, 278 ShowHostname: false, 279 } 280 281 creator := func() common.Executor { 282 return &executor{ 283 AbstractExecutor: executors.AbstractExecutor{ 284 ExecutorOptions: options, 285 }, 286 } 287 } 288 289 featuresUpdater := func(features *common.FeaturesInfo) { 290 features.Variables = true 291 features.Shared = true 292 } 293 294 common.RegisterExecutor("custom", executors.DefaultExecutorProvider{ 295 Creator: creator, 296 FeaturesUpdater: featuresUpdater, 297 DefaultShellName: options.Shell.Shell, 298 }) 299 }