Example #1
0
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
}
Example #2
0
File: hook.go Project: Ablu/drone
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)},
		})
	}

}