github.com/secure-build/gitlab-runner@v12.5.0+incompatible/helpers/gitlab_ci_yaml_parser/parser.go (about) 1 package gitlab_ci_yaml_parser 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "strconv" 8 "strings" 9 10 "gitlab.com/gitlab-org/gitlab-runner/common" 11 12 "gopkg.in/yaml.v2" 13 ) 14 15 type GitLabCiYamlParser struct { 16 filename string 17 jobName string 18 config DataBag 19 jobConfig DataBag 20 } 21 22 func (c *GitLabCiYamlParser) parseFile() (err error) { 23 data, err := ioutil.ReadFile(c.filename) 24 if err != nil { 25 return err 26 } 27 28 config := make(DataBag) 29 err = yaml.Unmarshal(data, config) 30 if err != nil { 31 return err 32 } 33 34 err = config.Sanitize() 35 if err != nil { 36 return err 37 } 38 39 c.config = config 40 41 return 42 } 43 44 func (c *GitLabCiYamlParser) loadJob() (err error) { 45 jobConfig, ok := c.config.GetSubOptions(c.jobName) 46 if !ok { 47 return fmt.Errorf("no job named %q", c.jobName) 48 } 49 50 c.jobConfig = jobConfig 51 52 return 53 } 54 55 func (c *GitLabCiYamlParser) prepareJobInfo(job *common.JobResponse) (err error) { 56 job.JobInfo = common.JobInfo{ 57 Name: c.jobName, 58 } 59 60 if stage, ok := c.jobConfig.GetString("stage"); ok { 61 job.JobInfo.Stage = stage 62 } else { 63 job.JobInfo.Stage = "test" 64 } 65 66 return 67 } 68 69 func (c *GitLabCiYamlParser) getCommands(commands interface{}) (common.StepScript, error) { 70 if lines, ok := commands.([]interface{}); ok { 71 var steps common.StepScript 72 for _, line := range lines { 73 if lineText, ok := line.(string); ok { 74 steps = append(steps, lineText) 75 } else { 76 return common.StepScript{}, errors.New("unsupported script") 77 } 78 } 79 return steps, nil 80 } else if text, ok := commands.(string); ok { 81 return common.StepScript(strings.Split(text, "\n")), nil 82 } else if commands != nil { 83 return common.StepScript{}, errors.New("unsupported script") 84 } 85 86 return common.StepScript{}, nil 87 } 88 89 func (c *GitLabCiYamlParser) prepareSteps(job *common.JobResponse) (err error) { 90 if c.jobConfig["script"] == nil { 91 err = fmt.Errorf("missing 'script' for job") 92 return 93 } 94 95 var scriptCommands, afterScriptCommands common.StepScript 96 97 // get before_script 98 beforeScript, err := c.getCommands(c.config["before_script"]) 99 if err != nil { 100 return 101 } 102 103 // get job before_script 104 jobBeforeScript, err := c.getCommands(c.jobConfig["before_script"]) 105 if err != nil { 106 return 107 } 108 109 if len(jobBeforeScript) < 1 { 110 scriptCommands = beforeScript 111 } else { 112 scriptCommands = jobBeforeScript 113 } 114 115 // get script 116 script, err := c.getCommands(c.jobConfig["script"]) 117 if err != nil { 118 return 119 } 120 scriptCommands = append(scriptCommands, script...) 121 122 afterScriptCommands, err = c.getCommands(c.jobConfig["after_script"]) 123 if err != nil { 124 return 125 } 126 127 job.Steps = common.Steps{ 128 common.Step{ 129 Name: common.StepNameScript, 130 Script: scriptCommands, 131 Timeout: 3600, 132 When: common.StepWhenOnSuccess, 133 AllowFailure: false, 134 }, 135 common.Step{ 136 Name: common.StepNameAfterScript, 137 Script: afterScriptCommands, 138 Timeout: 3600, 139 When: common.StepWhenAlways, 140 AllowFailure: false, 141 }, 142 } 143 144 return 145 } 146 147 func (c *GitLabCiYamlParser) buildDefaultVariables(job *common.JobResponse) (defaultVariables common.JobVariables, err error) { 148 defaultVariables = common.JobVariables{ 149 {Key: "CI", Value: "true", Public: true, Internal: true, File: false}, 150 {Key: "GITLAB_CI", Value: "true", Public: true, Internal: true, File: false}, 151 {Key: "CI_SERVER_NAME", Value: "GitLab CI", Public: true, Internal: true, File: false}, 152 {Key: "CI_SERVER_VERSION", Value: "", Public: true, Internal: true, File: false}, 153 {Key: "CI_SERVER_REVISION", Value: "", Public: true, Internal: true, File: false}, 154 {Key: "CI_PROJECT_ID", Value: strconv.Itoa(job.JobInfo.ProjectID), Public: true, Internal: true, File: false}, 155 {Key: "CI_JOB_ID", Value: strconv.Itoa(job.ID), Public: true, Internal: true, File: false}, 156 {Key: "CI_JOB_NAME", Value: job.JobInfo.Name, Public: true, Internal: true, File: false}, 157 {Key: "CI_JOB_STAGE", Value: job.JobInfo.Stage, Public: true, Internal: true, File: false}, 158 {Key: "CI_JOB_TOKEN", Value: job.Token, Public: true, Internal: true, File: false}, 159 {Key: "CI_REPOSITORY_URL", Value: job.GitInfo.RepoURL, Public: true, Internal: true, File: false}, 160 {Key: "CI_COMMIT_SHA", Value: job.GitInfo.Sha, Public: true, Internal: true, File: false}, 161 {Key: "CI_COMMIT_BEFORE_SHA", Value: job.GitInfo.BeforeSha, Public: true, Internal: true, File: false}, 162 {Key: "CI_COMMIT_REF_NAME", Value: job.GitInfo.Ref, Public: true, Internal: true, File: false}, 163 } 164 return 165 } 166 167 func (c *GitLabCiYamlParser) buildVariables(configVariables interface{}) (buildVariables common.JobVariables, err error) { 168 if variables, ok := configVariables.(map[string]interface{}); ok { 169 for key, value := range variables { 170 if valueText, ok := value.(string); ok { 171 buildVariables = append(buildVariables, common.JobVariable{ 172 Key: key, 173 Value: valueText, 174 Public: true, 175 }) 176 } else { 177 err = fmt.Errorf("invalid value for variable %q", key) 178 } 179 } 180 } else if configVariables != nil { 181 err = errors.New("unsupported variables") 182 } 183 184 return 185 } 186 187 func (c *GitLabCiYamlParser) prepareVariables(job *common.JobResponse) (err error) { 188 job.Variables = common.JobVariables{} 189 190 defaultVariables, err := c.buildDefaultVariables(job) 191 if err != nil { 192 return 193 } 194 195 job.Variables = append(job.Variables, defaultVariables...) 196 197 globalVariables, err := c.buildVariables(c.config["variables"]) 198 if err != nil { 199 return 200 } 201 202 job.Variables = append(job.Variables, globalVariables...) 203 204 jobVariables, err := c.buildVariables(c.jobConfig["variables"]) 205 if err != nil { 206 return 207 } 208 209 job.Variables = append(job.Variables, jobVariables...) 210 211 return 212 } 213 214 func (c *GitLabCiYamlParser) prepareImage(job *common.JobResponse) (err error) { 215 job.Image = common.Image{} 216 217 if imageName, ok := c.jobConfig.GetString("image"); ok { 218 job.Image.Name = imageName 219 return 220 } 221 222 if imageDefinition, ok := c.jobConfig.GetSubOptions("image"); ok { 223 job.Image.Name, _ = imageDefinition.GetString("name") 224 job.Image.Entrypoint, _ = imageDefinition.GetStringSlice("entrypoint") 225 return 226 } 227 228 if imageName, ok := c.config.GetString("image"); ok { 229 job.Image.Name = imageName 230 return 231 } 232 233 if imageDefinition, ok := c.config.GetSubOptions("image"); ok { 234 job.Image.Name, _ = imageDefinition.GetString("name") 235 job.Image.Entrypoint, _ = imageDefinition.GetStringSlice("entrypoint") 236 return 237 } 238 239 return 240 } 241 242 func parseExtendedServiceDefinitionMap(serviceDefinition map[interface{}]interface{}) (image common.Image) { 243 service := make(DataBag) 244 for key, value := range serviceDefinition { 245 service[key.(string)] = value 246 } 247 248 image.Name, _ = service.GetString("name") 249 image.Alias, _ = service.GetString("alias") 250 image.Command, _ = service.GetStringSlice("command") 251 image.Entrypoint, _ = service.GetStringSlice("entrypoint") 252 return 253 } 254 255 func (c *GitLabCiYamlParser) prepareServices(job *common.JobResponse) (err error) { 256 job.Services = common.Services{} 257 258 if servicesMap, ok := getOptions("services", c.jobConfig, c.config); ok { 259 for _, service := range servicesMap { 260 if serviceName, ok := service.(string); ok { 261 job.Services = append(job.Services, common.Image{ 262 Name: serviceName, 263 }) 264 continue 265 } 266 267 if serviceDefinition, ok := service.(map[interface{}]interface{}); ok { 268 job.Services = append(job.Services, parseExtendedServiceDefinitionMap(serviceDefinition)) 269 } 270 } 271 } 272 273 return 274 } 275 276 func (c *GitLabCiYamlParser) prepareArtifacts(job *common.JobResponse) (err error) { 277 var ok bool 278 279 artifactsMap := getOptionsMap("artifacts", c.jobConfig, c.config) 280 281 artifactsPaths, _ := artifactsMap.GetSlice("paths") 282 paths := common.ArtifactPaths{} 283 for _, path := range artifactsPaths { 284 paths = append(paths, path.(string)) 285 } 286 287 var artifactsName string 288 if artifactsName, ok = artifactsMap.GetString("name"); !ok { 289 artifactsName = "" 290 } 291 292 var artifactsUntracked interface{} 293 if artifactsUntracked, ok = artifactsMap.Get("untracked"); !ok { 294 artifactsUntracked = false 295 } 296 297 var artifactsWhen string 298 if artifactsWhen, ok = artifactsMap.GetString("when"); !ok { 299 artifactsWhen = string(common.ArtifactWhenOnSuccess) 300 } 301 302 var artifactsExpireIn string 303 if artifactsExpireIn, ok = artifactsMap.GetString("expireIn"); !ok { 304 artifactsExpireIn = "" 305 } 306 307 job.Artifacts = make(common.Artifacts, 1) 308 job.Artifacts[0] = common.Artifact{ 309 Name: artifactsName, 310 Untracked: artifactsUntracked.(bool), 311 Paths: paths, 312 When: common.ArtifactWhen(artifactsWhen), 313 ExpireIn: artifactsExpireIn, 314 } 315 316 return 317 } 318 319 func (c *GitLabCiYamlParser) prepareCache(job *common.JobResponse) (err error) { 320 var ok bool 321 322 cacheMap := getOptionsMap("cache", c.jobConfig, c.config) 323 324 cachePaths, _ := cacheMap.GetSlice("paths") 325 paths := common.ArtifactPaths{} 326 for _, path := range cachePaths { 327 paths = append(paths, path.(string)) 328 } 329 330 var cacheKey string 331 if cacheKey, ok = cacheMap.GetString("key"); !ok { 332 cacheKey = "" 333 } 334 335 var cacheUntracked interface{} 336 if cacheUntracked, ok = cacheMap.Get("untracked"); !ok { 337 cacheUntracked = false 338 } 339 340 job.Cache = make(common.Caches, 1) 341 job.Cache[0] = common.Cache{ 342 Key: cacheKey, 343 Untracked: cacheUntracked.(bool), 344 Paths: paths, 345 } 346 347 return 348 } 349 350 func (c *GitLabCiYamlParser) ParseYaml(job *common.JobResponse) (err error) { 351 err = c.parseFile() 352 if err != nil { 353 return err 354 } 355 356 err = c.loadJob() 357 if err != nil { 358 return err 359 } 360 361 parsers := []struct { 362 method func(job *common.JobResponse) error 363 }{ 364 {c.prepareJobInfo}, 365 {c.prepareSteps}, 366 {c.prepareVariables}, 367 {c.prepareImage}, 368 {c.prepareServices}, 369 {c.prepareArtifacts}, 370 {c.prepareCache}, 371 } 372 373 for _, parser := range parsers { 374 err = parser.method(job) 375 if err != nil { 376 return err 377 } 378 } 379 380 return nil 381 } 382 383 func NewGitLabCiYamlParser(jobName string) *GitLabCiYamlParser { 384 return &GitLabCiYamlParser{ 385 filename: ".gitlab-ci.yml", 386 jobName: jobName, 387 } 388 }