예제 #1
0
// PostHook accepts a post-commit hook and parses the payload
// in order to trigger a build. The payload is specified to the
// remote system (ie GitHub) and will therefore get parsed by
// the appropriate remote plugin.
//
//     POST /api/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}
//
func PostCommit(c web.C, w http.ResponseWriter, r *http.Request) {
	var ctx = context.FromC(c)
	var (
		branch = c.URLParams["branch"]
		hash   = c.URLParams["commit"]
		host   = c.URLParams["host"]
		repo   = ToRepo(c)
		remote = remote.Lookup(host)
	)

	commit, err := datastore.GetCommitSha(ctx, repo, branch, hash)
	if err != nil {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	if commit.Status == model.StatusStarted ||
		commit.Status == model.StatusEnqueue {
		w.WriteHeader(http.StatusConflict)
		return
	}

	commit.Status = model.StatusEnqueue
	commit.Started = 0
	commit.Finished = 0
	commit.Duration = 0
	if err := datastore.PutCommit(ctx, commit); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	owner, err := datastore.GetUser(ctx, repo.UserID)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// Request a new token and update
	user_token, err := remote.GetToken(owner)
	if user_token != nil {
		owner.Access = user_token.AccessToken
		owner.Secret = user_token.RefreshToken
		owner.TokenExpiry = user_token.Expiry
		datastore.PutUser(ctx, owner)
	} else if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// drop the items on the queue
	go worker.Do(ctx, &worker.Work{
		User:   owner,
		Repo:   repo,
		Commit: commit,
		Host:   httputil.GetURL(r),
	})

	w.WriteHeader(http.StatusOK)
}
예제 #2
0
func (d *Docker) Do(c context.Context, r *worker.Work) {

	// ensure that we can recover from any panics to
	// avoid bringing down the entire application.
	defer func() {
		if e := recover(); e != nil {
			log.Printf("%s: %s", e, debug.Stack())
		}
	}()

	// mark the build as Started and update the database
	r.Commit.Status = model.StatusStarted
	r.Commit.Started = time.Now().UTC().Unix()

	datastore.PutCommit(c, r.Commit)

	// notify all listeners that the build is started
	commitc := pubsub.Register(c, "_global")
	commitc.Publish(r)
	stdoutc := pubsub.RegisterOpts(c, r.Commit.ID, pubsub.ConsoleOpts)
	defer pubsub.Unregister(c, r.Commit.ID)

	// create a special buffer that will also
	// write to a websocket channel
	buf := pubsub.NewBuffer(stdoutc)

	// parse the parameters and build script. The script has already
	// been parsed in the hook, so we can be confident it will succeed.
	// that being said, we should clean this up
	params, err := r.Repo.ParamMap()
	if err != nil {
		log.Printf("Error parsing PARAMS for %s/%s, Err: %s", r.Repo.Owner, r.Repo.Name, err.Error())
	}
	script, err := script.ParseBuild(script.Inject(r.Commit.Config, params))
	if err != nil {
		log.Printf("Error parsing YAML for %s/%s, Err: %s", r.Repo.Owner, r.Repo.Name, err.Error())
	}

	// TODO: handle error better?
	buildNumber, err := datastore.GetBuildNumber(c, r.Commit)
	if err != nil {
		log.Printf("Unable to fetch build number, Err: %s", err.Error())
	}
	script.Env = append(script.Env, fmt.Sprintf("DRONE_BUILD_NUMBER=%d", buildNumber))
	script.Env = append(script.Env, fmt.Sprintf("CI_BUILD_NUMBER=%d", buildNumber))

	path := r.Repo.Host + "/" + r.Repo.Owner + "/" + r.Repo.Name
	repo := &repo.Repo{
		Name:    path,
		Path:    r.Repo.CloneURL,
		Branch:  r.Commit.Branch,
		Commit:  r.Commit.Sha,
		PR:      r.Commit.PullRequest,
		Private: r.Repo.Private,
		Dir:     filepath.ToSlash(filepath.Join("/var/cache/drone/src", git.GitPath(script.Git, path))),
		Depth:   git.GitDepth(script.Git),
	}

	// append private parameters to the environment
	// variable section of the .drone.yml file, if
	// this is trusted
	if params != nil && repo.IsTrusted() {
		for k, v := range params {
			script.Env = append(script.Env, k+"="+v)
		}
	}

	priorCommit, _ := datastore.GetCommitPrior(c, r.Commit)

	// send all "started" notifications
	if script.Notifications == nil {
		script.Notifications = &notify.Notification{}
	}
	script.Notifications.Send(&model.Request{
		User:   r.User,
		Repo:   r.Repo,
		Commit: r.Commit,
		Host:   r.Host,
		Prior:  priorCommit,
	})

	// create an instance of the Docker builder
	builder := build.New(d.docker)
	builder.Build = script
	builder.Repo = repo
	builder.Stdout = buf
	builder.Timeout = time.Duration(r.Repo.Timeout) * time.Second
	builder.Privileged = r.Repo.Privileged

	if repo.IsTrusted() {
		builder.Key = []byte(r.Repo.PrivateKey)
	}

	// run the build
	err = builder.Run()

	// update the build status based on the results
	// from the build runner.
	switch {
	case err != nil:
		r.Commit.Status = model.StatusError
		log.Printf("Error building %s, Err: %s", r.Commit.Sha, err)
		buf.WriteString(err.Error())
	case builder.BuildState == nil:
		r.Commit.Status = model.StatusFailure
	case builder.BuildState.ExitCode != 0:
		r.Commit.Status = model.StatusFailure
	default:
		r.Commit.Status = model.StatusSuccess
	}

	// calcualte the build finished and duration details and
	// update the commit
	r.Commit.Finished = time.Now().UTC().Unix()
	r.Commit.Duration = (r.Commit.Finished - r.Commit.Started)
	datastore.PutCommit(c, r.Commit)
	blobstore.Put(c, filepath.Join(r.Repo.Host, r.Repo.Owner, r.Repo.Name, r.Commit.Branch, r.Commit.Sha), buf.Bytes())

	// notify all listeners that the build is finished
	commitc.Publish(r)

	priorCommit, _ = datastore.GetCommitPrior(c, r.Commit)

	// send all "finished" notifications
	script.Notifications.Send(&model.Request{
		User:   r.User,
		Repo:   r.Repo,
		Commit: r.Commit,
		Host:   r.Host,
		Prior:  priorCommit,
	})
}