func exec(c *cli.Context) error { sigterm := make(chan os.Signal, 1) cancelc := make(chan bool, 1) signal.Notify(sigterm, os.Interrupt) go func() { <-sigterm cancelc <- true }() path := c.Args().First() if path == "" { path = ".drone.yml" } path, _ = filepath.Abs(path) dir := filepath.Dir(path) file, err := ioutil.ReadFile(path) if err != nil { return err } engine, err := docker.New( c.String("docker-host"), c.String("docker-cert-path"), c.Bool("docker-tls-verify"), ) if err != nil { return err } a := agent.Agent{ Update: agent.NoopUpdateFunc, Logger: agent.TermLoggerFunc, Engine: engine, Timeout: c.Duration("timeout.inactivity"), Platform: "linux/amd64", Namespace: c.String("namespace"), Disable: c.StringSlice("plugin"), Escalate: c.StringSlice("privileged"), Netrc: []string{}, Local: dir, Pull: c.Bool("pull"), } payload := &model.Work{ Yaml: string(file), Verified: c.BoolT("yaml.verified"), Signed: c.BoolT("yaml.signed"), Repo: &model.Repo{ FullName: c.String("repo.fullname"), Owner: c.String("repo.owner"), Name: c.String("repo.name"), Kind: c.String("repo.type"), Link: c.String("repo.link"), Branch: c.String("repo.branch"), Avatar: c.String("repo.avatar"), Timeout: int64(c.Duration("timeout").Minutes()), IsPrivate: c.Bool("repo.private"), IsTrusted: c.Bool("repo.trusted"), Clone: c.String("remote.url"), }, System: &model.System{ Link: c.GlobalString("server"), }, Secrets: getSecrets(c), Netrc: &model.Netrc{ Login: c.String("netrc.username"), Password: c.String("netrc.password"), Machine: c.String("netrc.machine"), }, Build: &model.Build{ Commit: c.String("commit.sha"), Branch: c.String("commit.branch"), Ref: c.String("commit.ref"), Link: c.String("commit.link"), Message: c.String("commit.message"), Author: c.String("commit.author.name"), Email: c.String("commit.author.email"), Avatar: c.String("commit.author.avatar"), Number: c.Int("build.number"), Event: c.String("build.event"), Deploy: c.String("build.deploy"), }, BuildLast: &model.Build{ Number: c.Int("prev.build.number"), Status: c.String("prev.build.status"), Commit: c.String("prev.commit.sha"), }, } if len(c.StringSlice("matrix")) > 0 { p := *payload p.Job = &model.Job{ Environment: getMatrix(c), } return a.Run(&p, cancelc) } axes, err := yaml.ParseMatrix(file) if err != nil { return err } if len(axes) == 0 { axes = append(axes, yaml.Axis{}) } var jobs []*model.Job count := 0 for _, axis := range axes { jobs = append(jobs, &model.Job{ Number: count, Environment: axis, }) count++ } for _, job := range jobs { fmt.Printf("Running Matrix job #%d\n", job.Number) p := *payload p.Job = job if err := a.Run(&p, cancelc); err != nil { return err } } return nil }
func PostHook(c *gin.Context) { remote_ := remote.FromContext(c) tmprepo, build, err := remote_.Hook(c.Request) if err != nil { log.Errorf("failure to parse hook. %s", err) c.AbortWithError(400, err) return } if build == nil { c.Writer.WriteHeader(200) return } if tmprepo == nil { log.Errorf("failure to ascertain repo from hook.") c.Writer.WriteHeader(400) return } // skip the build if any case-insensitive combination of the words "skip" and "ci" // wrapped in square brackets appear in the commit message skipMatch := skipRe.FindString(build.Message) if len(skipMatch) > 0 { log.Infof("ignoring hook. %s found in %s", skipMatch, build.Commit) c.Writer.WriteHeader(204) return } repo, err := store.GetRepoOwnerName(c, tmprepo.Owner, tmprepo.Name) if err != nil { log.Errorf("failure to find repo %s/%s from hook. %s", tmprepo.Owner, tmprepo.Name, err) c.AbortWithError(404, err) return } // get the token and verify the hook is authorized parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) { return repo.Hash, nil }) if err != nil { log.Errorf("failure to parse token from hook for %s. %s", repo.FullName, err) c.AbortWithError(400, err) return } if parsed.Text != repo.FullName { log.Errorf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsed.Text) c.AbortWithStatus(403) return } if repo.UserID == 0 { log.Warnf("ignoring hook. repo %s has no owner.", repo.FullName) c.Writer.WriteHeader(204) return } var skipped = true if (build.Event == model.EventPush && repo.AllowPush) || (build.Event == model.EventPull && repo.AllowPull) || (build.Event == model.EventDeploy && repo.AllowDeploy) || (build.Event == model.EventTag && repo.AllowTag) { skipped = false } if skipped { log.Infof("ignoring hook. repo %s is disabled for %s events.", repo.FullName, build.Event) c.Writer.WriteHeader(204) return } user, err := store.GetUser(c, repo.UserID) if err != nil { log.Errorf("failure to find repo owner %s. %s", repo.FullName, err) c.AbortWithError(500, err) return } // if there is no email address associated with the pull request, // we lookup the email address based on the authors github login. // // my initial hesitation with this code is that it has the ability // to expose your email address. At the same time, your email address // is already exposed in the public .git log. So while some people will // a small number of people will probably be upset by this, I'm not sure // it is actually that big of a deal. if len(build.Email) == 0 { author, err := store.GetUserLogin(c, build.Author) if err == nil { build.Email = author.Email } } // if the remote has a refresh token, the current access token // may be stale. Therefore, we should refresh prior to dispatching // the job. if refresher, ok := remote_.(remote.Refresher); ok { ok, _ := refresher.Refresh(user) if ok { store.UpdateUser(c, user) } } // fetch the build file from the database config := ToConfig(c) raw, err := remote_.File(user, repo, build, config.Yaml) if err != nil { log.Errorf("failure to get build config for %s. %s", repo.FullName, err) c.AbortWithError(404, err) return } sec, err := remote_.File(user, repo, build, config.Shasum) if err != nil { log.Debugf("cannot find build secrets for %s. %s", repo.FullName, err) // NOTE we don't exit on failure. The sec file is optional } axes, err := yaml.ParseMatrix(raw) if err != nil { c.String(500, "Failed to parse yaml file or calculate matrix. %s", err) return } if len(axes) == 0 { axes = append(axes, yaml.Axis{}) } netrc, err := remote_.Netrc(user, repo) if err != nil { c.String(500, "Failed to generate netrc file. %s", err) return } // verify the branches can be built vs skipped branches := yaml.ParseBranch(raw) if !branches.Match(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy { c.String(200, "Branch does not match restrictions defined in yaml") return } signature, err := jose.ParseSigned(string(sec)) if err != nil { log.Debugf("cannot parse .drone.yml.sig file. %s", err) } else if len(sec) == 0 { log.Debugf("cannot parse .drone.yml.sig file. empty file") } else { build.Signed = true output, err := signature.Verify([]byte(repo.Hash)) if err != nil { log.Debugf("cannot verify .drone.yml.sig file. %s", err) } else if string(output) != string(raw) { log.Debugf("cannot verify .drone.yml.sig file. no match") } else { build.Verified = true } } // update some build fields build.Status = model.StatusPending build.RepoID = repo.ID // and use a transaction var jobs []*model.Job for num, axis := range axes { jobs = append(jobs, &model.Job{ BuildID: build.ID, Number: num + 1, Status: model.StatusPending, Environment: axis, }) } err = store.CreateBuild(c, build, jobs...) if err != nil { log.Errorf("failure to save commit for %s. %s", repo.FullName, err) c.AbortWithError(500, err) return } c.JSON(200, build) url := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number) err = remote_.Status(user, repo, build, url) if err != nil { log.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number) } // get the previous build so that we can send // on status change notifications last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID) secs, err := store.GetSecretList(c, repo) if err != nil { log.Errorf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err) } bus.Publish(c, bus.NewBuildEvent(bus.Enqueued, repo, build)) for _, job := range jobs { queue.Publish(c, &queue.Work{ Signed: build.Signed, Verified: build.Verified, User: user, Repo: repo, Build: build, BuildLast: last, Job: job, Netrc: netrc, Yaml: string(raw), Secrets: secs, System: &model.System{Link: httputil.GetURL(c.Request)}, }) } }