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  }