github.com/ssube/gitlab-ci-multi-runner@v1.2.1-0.20160607142738-b8d1285632e6/common/build.go (about) 1 package common 2 3 import ( 4 "errors" 5 "fmt" 6 "net/url" 7 "os" 8 "path" 9 "strconv" 10 "strings" 11 12 "github.com/Sirupsen/logrus" 13 "gitlab.com/gitlab-org/gitlab-ci-multi-runner/helpers" 14 "time" 15 ) 16 17 type BuildState string 18 19 const ( 20 Pending BuildState = "pending" 21 Running = "running" 22 Failed = "failed" 23 Success = "success" 24 ) 25 26 type Build struct { 27 GetBuildResponse `yaml:",inline"` 28 29 Trace BuildTrace 30 BuildAbort chan os.Signal `json:"-" yaml:"-"` 31 RootDir string `json:"-" yaml:"-"` 32 BuildDir string `json:"-" yaml:"-"` 33 CacheDir string `json:"-" yaml:"-"` 34 Hostname string `json:"-" yaml:"-"` 35 Runner *RunnerConfig `json:"runner"` 36 ExecutorData ExecutorData 37 38 // Unique ID for all running builds on this runner 39 RunnerID int `json:"runner_id"` 40 41 // Unique ID for all running builds on this runner and this project 42 ProjectRunnerID int `json:"project_runner_id"` 43 } 44 45 func (b *Build) log() *logrus.Entry { 46 return b.Runner.Log().WithField("build", b.ID) 47 } 48 49 func (b *Build) ProjectUniqueName() string { 50 return fmt.Sprintf("runner-%s-project-%d-concurrent-%d", 51 b.Runner.ShortDescription(), b.ProjectID, b.ProjectRunnerID) 52 } 53 54 func (b *Build) ProjectSlug() (string, error) { 55 url, err := url.Parse(b.RepoURL) 56 if err != nil { 57 return "", err 58 } 59 if url.Host == "" { 60 return "", errors.New("only URI reference supported") 61 } 62 63 slug := url.Path 64 slug = strings.TrimSuffix(slug, ".git") 65 slug = path.Clean(slug) 66 if slug == "." { 67 return "", errors.New("invalid path") 68 } 69 if strings.Contains(slug, "..") { 70 return "", errors.New("it doesn't look like a valid path") 71 } 72 return slug, nil 73 } 74 75 func (b *Build) ProjectUniqueDir(sharedDir bool) string { 76 dir, err := b.ProjectSlug() 77 if err != nil { 78 dir = fmt.Sprintf("project-%d", b.ProjectID) 79 } 80 81 // for shared dirs path is constructed like this: 82 // <some-path>/runner-short-id/concurrent-id/group-name/project-name/ 83 // ex.<some-path>/01234567/0/group/repo/ 84 if sharedDir { 85 dir = path.Join( 86 fmt.Sprintf("%s", b.Runner.ShortDescription()), 87 fmt.Sprintf("%d", b.ProjectRunnerID), 88 dir, 89 ) 90 } 91 return dir 92 } 93 94 func (b *Build) FullProjectDir() string { 95 return helpers.ToSlash(b.BuildDir) 96 } 97 98 func (b *Build) StartBuild(rootDir, cacheDir string, sharedDir bool) { 99 b.RootDir = rootDir 100 b.BuildDir = path.Join(rootDir, b.ProjectUniqueDir(sharedDir)) 101 b.CacheDir = path.Join(cacheDir, b.ProjectUniqueDir(false)) 102 } 103 104 func (b *Build) executeScript(buildScript *ShellScript, executor Executor, abort chan interface{}) error { 105 // Execute pre script (git clone, cache restore, artifacts download) 106 err := executor.Run(ExecutorCommand{ 107 Script: buildScript.PreScript, 108 Predefined: true, 109 Abort: abort, 110 }) 111 112 if err == nil { 113 // Execute build script (user commands) 114 err = executor.Run(ExecutorCommand{ 115 Script: buildScript.BuildScript, 116 Abort: abort, 117 }) 118 119 // Execute after script (user commands) 120 if buildScript.AfterScript != "" { 121 timeoutCh := make(chan interface{}) 122 go func() { 123 timeoutCh <- <-time.After(time.Minute * 5) 124 }() 125 executor.Run(ExecutorCommand{ 126 Script: buildScript.AfterScript, 127 Abort: timeoutCh, 128 }) 129 } 130 } 131 132 // Execute post script (cache store, artifacts upload) 133 if err == nil { 134 err = executor.Run(ExecutorCommand{ 135 Script: buildScript.PostScript, 136 Predefined: true, 137 Abort: abort, 138 }) 139 } 140 141 return err 142 } 143 144 func (b *Build) run(executor Executor) (err error) { 145 buildTimeout := b.Timeout 146 if buildTimeout <= 0 { 147 buildTimeout = DefaultTimeout 148 } 149 150 buildCanceled := make(chan bool) 151 buildFinish := make(chan error) 152 buildAbort := make(chan interface{}) 153 154 // Wait for cancel notification 155 b.Trace.Notify(func() { 156 buildCanceled <- true 157 }) 158 159 // Run build script 160 go func() { 161 buildFinish <- b.executeScript(executor.ShellScript(), executor, buildAbort) 162 }() 163 164 // Wait for signals: cancel, timeout, abort or finish 165 b.log().Debugln("Waiting for signals...") 166 select { 167 case <-buildCanceled: 168 err = errors.New("canceled") 169 170 case <-time.After(time.Duration(buildTimeout) * time.Second): 171 err = fmt.Errorf("execution took longer than %v seconds", buildTimeout) 172 173 case signal := <-b.BuildAbort: 174 err = fmt.Errorf("aborted: %v", signal) 175 176 case err = <-buildFinish: 177 return err 178 } 179 180 b.log().Debugln("Waiting for build to finish...", err) 181 182 // Wait till we receive that build did finish 183 for { 184 select { 185 case buildAbort <- true: 186 case <-buildFinish: 187 return err 188 } 189 } 190 } 191 192 func (b *Build) Run(globalConfig *Config, trace BuildTrace) (err error) { 193 defer func() { 194 if err != nil { 195 trace.Fail(err) 196 } else { 197 trace.Success() 198 } 199 }() 200 b.Trace = trace 201 202 executor := NewExecutor(b.Runner.Executor) 203 if executor == nil { 204 fmt.Fprint(trace, "Executor not found:", b.Runner.Executor) 205 return errors.New("executor not found") 206 } 207 defer executor.Cleanup() 208 209 err = executor.Prepare(globalConfig, b.Runner, b) 210 if err == nil { 211 err = b.run(executor) 212 } 213 executor.Finish(err) 214 return err 215 } 216 217 func (b *Build) String() string { 218 return helpers.ToYAML(b) 219 } 220 221 func (b *Build) GetDefaultVariables() BuildVariables { 222 return BuildVariables{ 223 {"CI", "true", true, true, false}, 224 {"CI_BUILD_REF", b.Sha, true, true, false}, 225 {"CI_BUILD_BEFORE_SHA", b.BeforeSha, true, true, false}, 226 {"CI_BUILD_REF_NAME", b.RefName, true, true, false}, 227 {"CI_BUILD_ID", strconv.Itoa(b.ID), true, true, false}, 228 {"CI_BUILD_REPO", b.RepoURL, true, true, false}, 229 {"CI_BUILD_TOKEN", b.Token, true, true, false}, 230 {"CI_PROJECT_ID", strconv.Itoa(b.ProjectID), true, true, false}, 231 {"CI_PROJECT_DIR", b.FullProjectDir(), true, true, false}, 232 {"CI_SERVER", "yes", true, true, false}, 233 {"CI_SERVER_NAME", "GitLab CI", true, true, false}, 234 {"CI_SERVER_VERSION", "", true, true, false}, 235 {"CI_SERVER_REVISION", "", true, true, false}, 236 {"GITLAB_CI", "true", true, true, false}, 237 } 238 } 239 240 func (b *Build) GetAllVariables() BuildVariables { 241 variables := b.Runner.GetVariables() 242 variables = append(variables, b.GetDefaultVariables()...) 243 variables = append(variables, b.Variables...) 244 return variables.Expand() 245 }