github.com/drone/runner-go@v1.12.0/environ/environ.go (about)

     1  // Copyright 2019 Drone.IO Inc. All rights reserved.
     2  // Use of this source code is governed by the Polyform License
     3  // that can be found in the LICENSE file.
     4  
     5  // Package environ provides utilities for generating environment
     6  // variables for a build pipeline.
     7  package environ
     8  
     9  import (
    10  	"fmt"
    11  	"regexp"
    12  	"sort"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/drone/drone-go/drone"
    17  )
    18  
    19  // regular expression to extract the pull request number
    20  // from the git ref (e.g. refs/pulls/{d}/head)
    21  var re = regexp.MustCompile("\\d+")
    22  
    23  // System returns a set of environment variables containing
    24  // system metadata.
    25  func System(system *drone.System) map[string]string {
    26  	return map[string]string{
    27  		"CI":                    "true",
    28  		"DRONE":                 "true",
    29  		"DRONE_SYSTEM_PROTO":    system.Proto,
    30  		"DRONE_SYSTEM_HOST":     system.Host,
    31  		"DRONE_SYSTEM_HOSTNAME": system.Host,
    32  		"DRONE_SYSTEM_VERSION":  fmt.Sprint(system.Version),
    33  	}
    34  }
    35  
    36  // Repo returns a set of environment variables containing
    37  // repository metadata.
    38  func Repo(repo *drone.Repo) map[string]string {
    39  	return map[string]string{
    40  		"DRONE_REPO":            repo.Slug,
    41  		"DRONE_REPO_SCM":        repo.SCM,
    42  		"DRONE_REPO_OWNER":      repo.Namespace,
    43  		"DRONE_REPO_NAMESPACE":  repo.Namespace,
    44  		"DRONE_REPO_NAME":       repo.Name,
    45  		"DRONE_REPO_LINK":       repo.Link,
    46  		"DRONE_REPO_BRANCH":     repo.Branch,
    47  		"DRONE_REMOTE_URL":      repo.HTTPURL,
    48  		"DRONE_GIT_HTTP_URL":    repo.HTTPURL,
    49  		"DRONE_GIT_SSH_URL":     repo.SSHURL,
    50  		"DRONE_REPO_VISIBILITY": repo.Visibility,
    51  		"DRONE_REPO_PRIVATE":    fmt.Sprint(repo.Private),
    52  
    53  		// these are legacy configuration parameters for backward
    54  		// compatibility with drone 0.8. These are deprecated and
    55  		// should not be relied upon going forward.
    56  		"CI_REPO":         repo.Slug,
    57  		"CI_REPO_NAME":    repo.Slug,
    58  		"CI_REPO_LINK":    repo.Link,
    59  		"CI_REPO_REMOTE":  repo.HTTPURL,
    60  		"CI_REMOTE_URL":   repo.HTTPURL,
    61  		"CI_REPO_PRIVATE": fmt.Sprint(repo.Private),
    62  	}
    63  }
    64  
    65  // Stage returns a set of environment variables containing
    66  // stage metadata.
    67  func Stage(stage *drone.Stage) map[string]string {
    68  	env := map[string]string{
    69  		"DRONE_STAGE_KIND":       stage.Kind,
    70  		"DRONE_STAGE_TYPE":       stage.Type,
    71  		"DRONE_STAGE_NAME":       stage.Name,
    72  		"DRONE_STAGE_NUMBER":     fmt.Sprint(stage.Number),
    73  		"DRONE_STAGE_MACHINE":    stage.Machine,
    74  		"DRONE_STAGE_OS":         stage.OS,
    75  		"DRONE_STAGE_ARCH":       stage.Arch,
    76  		"DRONE_STAGE_VARIANT":    stage.Variant,
    77  		"DRONE_STAGE_VERSION":    fmt.Sprint(stage.Version),
    78  		"DRONE_STAGE_STATUS":     "success",
    79  		"DRONE_STAGE_STARTED":    fmt.Sprint(stage.Started),
    80  		"DRONE_STAGE_FINISHED":   fmt.Sprint(stage.Stopped),
    81  		"DRONE_STAGE_DEPENDS_ON": strings.Join(stage.DependsOn, ","),
    82  		"DRONE_CARD_PATH":        "/dev/stdout",
    83  	}
    84  	if isStageFailing(stage) {
    85  		env["DRONE_STAGE_STATUS"] = "failure"
    86  		env["DRONE_FAILED_STEPS"] = strings.Join(failedSteps(stage), ",")
    87  	}
    88  	if stage.Started == 0 {
    89  		env["DRONE_STAGE_STARTED"] = fmt.Sprint(time.Now().Unix())
    90  	}
    91  	if stage.Stopped == 0 {
    92  		env["DRONE_STAGE_FINISHED"] = fmt.Sprint(time.Now().Unix())
    93  	}
    94  	return env
    95  }
    96  
    97  // Step returns a set of environment variables containing the
    98  // step metadata.
    99  func Step(step *drone.Step) map[string]string {
   100  	return map[string]string{
   101  		"DRONE_STEP_NAME":   step.Name,
   102  		"DRONE_STEP_NUMBER": fmt.Sprint(step.Number),
   103  	}
   104  }
   105  
   106  // StepArgs returns a set of environment variables containing
   107  // the step name and number.
   108  func StepArgs(name string, number int64) map[string]string {
   109  	return map[string]string{
   110  		"DRONE_STEP_NAME":   name,
   111  		"DRONE_STEP_NUMBER": fmt.Sprint(number),
   112  	}
   113  }
   114  
   115  // StepName returns a set of environment variables containing
   116  // only the step name.
   117  func StepName(name string) map[string]string {
   118  	return map[string]string{
   119  		"DRONE_STEP_NAME": name,
   120  	}
   121  }
   122  
   123  // Build returns a set of environment variables containing
   124  // build metadata.
   125  func Build(build *drone.Build) map[string]string {
   126  	env := map[string]string{
   127  		"DRONE_BRANCH":               build.Target,
   128  		"DRONE_SOURCE_BRANCH":        build.Source,
   129  		"DRONE_TARGET_BRANCH":        build.Target,
   130  		"DRONE_COMMIT":               build.After,
   131  		"DRONE_COMMIT_SHA":           build.After,
   132  		"DRONE_COMMIT_BEFORE":        build.Before,
   133  		"DRONE_COMMIT_AFTER":         build.After,
   134  		"DRONE_COMMIT_REF":           build.Ref,
   135  		"DRONE_COMMIT_BRANCH":        build.Target,
   136  		"DRONE_COMMIT_LINK":          build.Link,
   137  		"DRONE_COMMIT_MESSAGE":       build.Message,
   138  		"DRONE_COMMIT_AUTHOR":        build.Author,
   139  		"DRONE_COMMIT_AUTHOR_EMAIL":  build.AuthorEmail,
   140  		"DRONE_COMMIT_AUTHOR_AVATAR": build.AuthorAvatar,
   141  		"DRONE_COMMIT_AUTHOR_NAME":   build.AuthorName,
   142  		"DRONE_BUILD_NUMBER":         fmt.Sprint(build.Number),
   143  		"DRONE_BUILD_PARENT":         fmt.Sprint(build.Parent),
   144  		"DRONE_BUILD_EVENT":          build.Event,
   145  		"DRONE_BUILD_ACTION":         build.Action,
   146  		"DRONE_BUILD_STATUS":         "success",
   147  		"DRONE_BUILD_DEBUG":          fmt.Sprint(build.Debug),
   148  		"DRONE_BUILD_CREATED":        fmt.Sprint(build.Created),
   149  		"DRONE_BUILD_STARTED":        fmt.Sprint(build.Started),
   150  		"DRONE_BUILD_FINISHED":       fmt.Sprint(build.Finished),
   151  		"DRONE_DEPLOY_TO":            build.Deploy,
   152  		"DRONE_DEPLOY_ID":            fmt.Sprint(build.DeployID),
   153  		"DRONE_BUILD_TRIGGER":        build.Trigger,
   154  
   155  		// these are legacy configuration parameters for backward
   156  		// compatibility with drone 0.8. These are deprecated and
   157  		// should not be relied upon going forward.
   158  		"CI_BUILD_NUMBER":         fmt.Sprint(build.Number),
   159  		"CI_PARENT_BUILD_NUMBER":  fmt.Sprint(build.Parent),
   160  		"CI_BUILD_CREATED":        fmt.Sprint(build.Created),
   161  		"CI_BUILD_STARTED":        fmt.Sprint(build.Started),
   162  		"CI_BUILD_FINISHED":       fmt.Sprint(build.Finished),
   163  		"CI_BUILD_STATUS":         build.Status,
   164  		"CI_BUILD_EVENT":          build.Event,
   165  		"CI_BUILD_LINK":           build.Link,
   166  		"CI_BUILD_TARGET":         build.Deploy,
   167  		"CI_COMMIT_SHA":           build.After,
   168  		"CI_COMMIT_REF":           build.Ref,
   169  		"CI_COMMIT_BRANCH":        build.Target,
   170  		"CI_COMMIT_MESSAGE":       build.Message,
   171  		"CI_COMMIT_AUTHOR":        build.Author,
   172  		"CI_COMMIT_AUTHOR_NAME":   build.AuthorName,
   173  		"CI_COMMIT_AUTHOR_EMAIL":  build.AuthorEmail,
   174  		"CI_COMMIT_AUTHOR_AVATAR": build.AuthorAvatar,
   175  	}
   176  	if isBuildFailing(build) {
   177  		env["DRONE_BUILD_STATUS"] = "failure"
   178  		env["DRONE_FAILED_STAGES"] = strings.Join(failedStages(build), ",")
   179  	}
   180  	if build.Started == 0 {
   181  		env["DRONE_BUILD_STARTED"] = fmt.Sprint(time.Now().Unix())
   182  	}
   183  	if build.Finished == 0 {
   184  		env["DRONE_BUILD_FINISHED"] = fmt.Sprint(time.Now().Unix())
   185  	}
   186  	if build.Event == drone.EventPullRequest {
   187  		env["DRONE_PULL_REQUEST"] = re.FindString(build.Ref)
   188  		env["DRONE_PULL_REQUEST_TITLE"] = build.Title
   189  	}
   190  	if strings.HasPrefix(build.Ref, "refs/tags/") {
   191  		tag := strings.TrimPrefix(build.Ref, "refs/tags/")
   192  		env["DRONE_TAG"] = tag
   193  		copyenv(versions(tag), env)
   194  		copyenv(calversions(tag), env)
   195  	}
   196  	return env
   197  }
   198  
   199  // Link returns a set of environment variables containing
   200  // resource urls to the build.
   201  func Link(repo *drone.Repo, build *drone.Build, system *drone.System) map[string]string {
   202  	return map[string]string{
   203  		"DRONE_BUILD_LINK": fmt.Sprintf(
   204  			"%s://%s/%s/%d",
   205  			system.Proto,
   206  			system.Host,
   207  			repo.Slug,
   208  			build.Number,
   209  		),
   210  	}
   211  }
   212  
   213  // Netrc returns a set of environment variables containing
   214  // the netrc file and credentials.
   215  func Netrc(netrc *drone.Netrc) map[string]string {
   216  	env := map[string]string{}
   217  	if netrc != nil && netrc.Machine != "" {
   218  		env["DRONE_NETRC_MACHINE"] = netrc.Machine
   219  		env["DRONE_NETRC_USERNAME"] = netrc.Login
   220  		env["DRONE_NETRC_PASSWORD"] = netrc.Password
   221  		env["DRONE_NETRC_FILE"] = fmt.Sprintf(
   222  			"machine %s login %s password %s",
   223  			netrc.Machine,
   224  			netrc.Login,
   225  			netrc.Password,
   226  		)
   227  	}
   228  	return env
   229  }
   230  
   231  // Combine is a helper function combines one or more maps of
   232  // environment variables into a single map.
   233  func Combine(env ...map[string]string) map[string]string {
   234  	c := map[string]string{}
   235  	for _, e := range env {
   236  		for k, v := range e {
   237  			c[k] = v
   238  		}
   239  	}
   240  	return c
   241  }
   242  
   243  // Slice is a helper function that converts a map of environment
   244  // variables to a slice of string values in key=value format.
   245  func Slice(env map[string]string) []string {
   246  	s := []string{}
   247  	for k, v := range env {
   248  		s = append(s, k+"="+v)
   249  	}
   250  	sort.Strings(s)
   251  	return s
   252  }
   253  
   254  // copyenv copies environment variables from the source map
   255  // to the destination map.
   256  func copyenv(src, dst map[string]string) {
   257  	for k, v := range src {
   258  		dst[k] = v
   259  	}
   260  }
   261  
   262  // helper function returns true of the build is failing.
   263  func isBuildFailing(build *drone.Build) bool {
   264  	if build.Status == drone.StatusError ||
   265  		build.Status == drone.StatusFailing ||
   266  		build.Status == drone.StatusKilled {
   267  		return true
   268  	}
   269  	for _, stage := range build.Stages {
   270  		if stage.Status == drone.StatusError ||
   271  			stage.Status == drone.StatusFailing ||
   272  			stage.Status == drone.StatusKilled {
   273  			return true
   274  		}
   275  	}
   276  	return false
   277  }
   278  
   279  // helper function returns true of the stage is failing.
   280  func isStageFailing(stage *drone.Stage) bool {
   281  	if stage.Status == drone.StatusError ||
   282  		stage.Status == drone.StatusFailing ||
   283  		stage.Status == drone.StatusKilled {
   284  		return true
   285  	}
   286  	for _, step := range stage.Steps {
   287  		if step.ErrIgnore && step.Status == drone.StatusFailing {
   288  			continue
   289  		}
   290  		if step.Status == drone.StatusError ||
   291  			step.Status == drone.StatusFailing ||
   292  			step.Status == drone.StatusKilled {
   293  			return true
   294  		}
   295  	}
   296  	return false
   297  }
   298  
   299  // helper function returns the failed steps.
   300  func failedSteps(stage *drone.Stage) []string {
   301  	var steps []string
   302  	for _, step := range stage.Steps {
   303  		if step.ErrIgnore && step.Status == drone.StatusFailing {
   304  			continue
   305  		}
   306  		if step.Status == drone.StatusError ||
   307  			step.Status == drone.StatusFailing ||
   308  			step.Status == drone.StatusKilled {
   309  			steps = append(steps, step.Name)
   310  		}
   311  	}
   312  	return steps
   313  }
   314  
   315  // helper function returns the failed stages.
   316  func failedStages(build *drone.Build) []string {
   317  	var stages []string
   318  	for _, stage := range build.Stages {
   319  		if stage.Status == drone.StatusError ||
   320  			stage.Status == drone.StatusFailing ||
   321  			stage.Status == drone.StatusKilled {
   322  			stages = append(stages, stage.Name)
   323  		}
   324  	}
   325  	return stages
   326  }