Example #1
0
// updateGitHubStatus is a helper function that will send
// the build status to GitHub using the Status API.
// see https://github.com/blog/1227-commit-status-api
func updateGitHubStatus(repo *Repo, commit *Commit) error {

	// convert from drone status to github status
	var message, status string
	switch commit.Status {
	case "Success":
		status = "success"
		message = "The build succeeded on drone.io"
	case "Failure":
		status = "failure"
		message = "The build failed on drone.io"
	case "Pending":
		status = "pending"
		message = "The build is pending on drone.io"
	default:
		status = "error"
		message = "The build errored on drone.io"
	}

	// get the system settings
	settings, _ := database.GetSettings()

	// get the user from the database
	// since we need his / her GitHub token
	user, err := database.GetUser(repo.UserID)
	if err != nil {
		return err
	}

	client := github.New(user.GithubToken)
	return client.Repos.CreateStatus(repo.Owner, repo.Name, status, settings.URL().String(), message, commit.Hash)
}
Example #2
0
func LinkGithub(w http.ResponseWriter, r *http.Request, u *User) error {

	// get settings from database
	settings := database.SettingsMust()

	// github OAuth2 Data
	var oauth = oauth2.Client{
		RedirectURL:      settings.URL().String() + "/auth/login/github",
		AccessTokenURL:   "https://" + settings.GitHubDomain + "/login/oauth/access_token",
		AuthorizationURL: "https://" + settings.GitHubDomain + "/login/oauth/authorize",
		ClientId:         settings.GitHubKey,
		ClientSecret:     settings.GitHubSecret,
	}

	// get the OAuth code
	code := r.FormValue("code")
	if len(code) == 0 {
		scope := "repo,repo:status,user:email"
		state := "FqB4EbagQ2o"
		redirect := oauth.AuthorizeRedirect(scope, state)
		http.Redirect(w, r, redirect, http.StatusSeeOther)
		return nil
	}

	// exchange code for an auth token
	token, err := oauth.GrantToken(code)
	if err != nil {
		log.Println("Error granting GitHub authorization token")
		return err
	}

	// create the client
	client := github.New(token.AccessToken)
	client.ApiUrl = settings.GitHubApiUrl

	// get the user information
	githubUser, err := client.Users.Current()
	if err != nil {
		log.Println("Error retrieving currently authenticated GitHub user")
		return err
	}

	// save the github token to the user account
	u.GithubToken = token.AccessToken
	u.GithubLogin = githubUser.Login
	if err := database.SaveUser(u); err != nil {
		log.Println("Error persisting user's GitHub auth token to the database")
		return err
	}

	http.Redirect(w, r, "/new/github.com", http.StatusSeeOther)
	return nil
}
Example #3
0
func RepoCreateGithub(w http.ResponseWriter, r *http.Request, u *User) error {
	teamName := r.FormValue("team")
	owner := r.FormValue("owner")
	name := r.FormValue("name")

	// get the github settings from the database
	settings := database.SettingsMust()

	// create the GitHub client
	client := github.New(u.GithubToken)
	githubRepo, err := client.Repos.Find(owner, name)
	if err != nil {
		return err
	}

	repo, err := NewGitHubRepo(owner, name, githubRepo.Private)
	if err != nil {
		return err
	}

	repo.UserID = u.ID
	repo.Private = githubRepo.Private

	// if the user chose to assign to a team account
	// we need to retrieve the team, verify the user
	// has access, and then set the team id.
	if len(teamName) > 0 {
		team, err := database.GetTeamSlug(teamName)
		if err != nil {
			log.Printf("error retrieving team %s", teamName)
			return err
		}

		// user must be an admin member of the team
		if ok, _ := database.IsMemberAdmin(u.ID, team.ID); !ok {
			return fmt.Errorf("Forbidden")
		}

		repo.TeamID = team.ID
	}

	// if the repository is private we'll need
	// to upload a github key to the repository
	if repo.Private {
		// name the key
		keyName := fmt.Sprintf("%s@%s", repo.Owner, settings.Domain)

		// create the github key, or update if one already exists
		_, err := client.RepoKeys.CreateUpdate(owner, name, repo.PublicKey, keyName)
		if err != nil {
			return fmt.Errorf("Unable to add Private Key to your GitHub repository")
		}
	} else {

	}

	// create a hook so that we get notified when code
	// is pushed to the repository and can execute a build.
	link := fmt.Sprintf("%s://%s/hook/github.com?id=%s", settings.Scheme, settings.Domain, repo.Slug)

	// add the hook
	if _, err := client.Hooks.CreateUpdate(owner, name, link); err != nil {
		return fmt.Errorf("Unable to add Hook to your GitHub repository. %s", err.Error())
	}

	// Save to the database
	if err := database.SaveRepo(repo); err != nil {
		log.Print("error saving new repository to the database")
		return err
	}

	return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
}
Example #4
0
// Processes a generic POST-RECEIVE hook and
// attempts to trigger a build.
func Hook(w http.ResponseWriter, r *http.Request) error {

	// if this is a pull request route
	// to a different handler
	if r.Header.Get("X-Github-Event") == "pull_request" {
		PullRequestHook(w, r)
		return nil
	}

	// get the payload of the message
	// this should contain a json representation of the
	// repository and commit details
	payload := r.FormValue("payload")

	// parse the github Hook payload
	hook, err := github.ParseHook([]byte(payload))
	if err != nil {
		println("could not parse hook")
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}

	// make sure this is being triggered because of a commit
	// and not something like a tag deletion or whatever
	if hook.IsTag() || hook.IsGithubPages() ||
		hook.IsHead() == false || hook.IsDeleted() {
		return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
	}

	// get the repo from the URL
	repoId := r.FormValue("id")

	// get the repo from the database, return error if not found
	repo, err := database.GetRepoSlug(repoId)
	if err != nil {
		return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
	}

	// Get the user that owns the repository
	user, err := database.GetUser(repo.UserID)
	if err != nil {
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}

	// Verify that the commit doesn't already exist.
	// We should never build the same commit twice.
	_, err = database.GetCommitHash(hook.Head.Id, repo.ID)
	if err != nil && err != sql.ErrNoRows {
		println("commit already exists")
		return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway)
	}

	// we really only need:
	//  * repo owner
	//  * repo name
	//  * repo host (github)
	//  * commit hash
	//  * commit timestamp
	//  * commit branch
	//  * commit message
	//  * commit author
	//  * pull request

	// once we have this data we could just send directly to the queue
	// and let it handle everything else

	commit := &Commit{}
	commit.RepoID = repo.ID
	commit.Branch = hook.Branch()
	commit.Hash = hook.Head.Id
	commit.Status = "Pending"
	commit.Created = time.Now().UTC()

	// extract the author and message from the commit
	// this is kind of experimental, since I don't know
	// what I'm doing here.
	if hook.Head != nil && hook.Head.Author != nil {
		commit.Message = hook.Head.Message
		commit.Timestamp = hook.Head.Timestamp
		commit.SetAuthor(hook.Head.Author.Email)
	} else if hook.Commits != nil && len(hook.Commits) > 0 && hook.Commits[0].Author != nil {
		commit.Message = hook.Commits[0].Message
		commit.Timestamp = hook.Commits[0].Timestamp
		commit.SetAuthor(hook.Commits[0].Author.Email)
	}

	// get the drone.yml file from GitHub
	client := github.New(user.GithubToken)
	content, err := client.Contents.FindRef(repo.Owner, repo.Name, ".drone.yml", commit.Hash)
	if err != nil {
		msg := "No .drone.yml was found in this repository.  You need to add one.\n"
		if err := saveFailedBuild(commit, msg); err != nil {
			return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		}
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}

	// decode the content.  Note: Not sure this will ever happen...it basically means a GitHub API issue
	raw, err := content.DecodeContent()
	if err != nil {
		msg := "Could not decode the yaml from GitHub.  Check that your .drone.yml is a valid yaml file.\n"
		if err := saveFailedBuild(commit, msg); err != nil {
			return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		}
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}

	// parse the build script
	buildscript, err := script.ParseBuild(raw)
	if err != nil {
		msg := "Could not parse your .drone.yml file.  It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n"
		if err := saveFailedBuild(commit, msg); err != nil {
			return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		}
		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
	}

	// save the commit to the database
	if err := database.SaveCommit(commit); err != nil {
		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
	}

	// save the build to the database
	build := &Build{}
	build.Slug = "1" // TODO
	build.CommitID = commit.ID
	build.Created = time.Now().UTC()
	build.Status = "Pending"
	if err := database.SaveBuild(build); err != nil {
		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
	}

	// notify websocket that a new build is pending
	//realtime.CommitPending(repo.UserID, repo.TeamID, repo.ID, commit.ID, repo.Private)
	//realtime.BuildPending(repo.UserID, repo.TeamID, repo.ID, commit.ID, build.ID, repo.Private)

	queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) //Push(repo, commit, build, buildscript)

	// OK!
	return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
}
Example #5
0
func PullRequestHook(w http.ResponseWriter, r *http.Request) {

	// get the payload of the message
	// this should contain a json representation of the
	// repository and commit details
	payload := r.FormValue("payload")

	println("GOT PR HOOK")
	println(payload)

	hook, err := github.ParsePullRequestHook([]byte(payload))
	if err != nil {
		RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	// ignore these
	if hook.Action != "opened" && hook.Action != "synchronize" {
		RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
		return
	}

	// get the repo from the URL
	repoId := r.FormValue("id")

	// get the repo from the database, return error if not found
	repo, err := database.GetRepoSlug(repoId)
	if err != nil {
		RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
		return
	}

	// Get the user that owns the repository
	user, err := database.GetUser(repo.UserID)
	if err != nil {
		RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	// Verify that the commit doesn't already exist.
	// We should enver build the same commit twice.
	_, err = database.GetCommitHash(hook.PullRequest.Head.Sha, repo.ID)
	if err != nil && err != sql.ErrNoRows {
		RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway)
		return
	}

	///////////////////////////////////////////////////////

	commit := &Commit{}
	commit.RepoID = repo.ID
	commit.Branch = hook.PullRequest.Head.Ref
	commit.Hash = hook.PullRequest.Head.Sha
	commit.Status = "Pending"
	commit.Created = time.Now().UTC()
	commit.Gravatar = hook.PullRequest.User.GravatarId
	commit.PullRequest = strconv.Itoa(hook.Number)
	commit.Message = hook.PullRequest.Title
	// label := p.PullRequest.Head.Labe

	// get the drone.yml file from GitHub
	client := github.New(user.GithubToken)
	content, err := client.Contents.FindRef(repo.Owner, repo.Name, ".drone.yml", commit.Hash) // TODO should this really be the hash??
	if err != nil {
		println(err.Error())
		RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	// decode the content
	raw, err := content.DecodeContent()
	if err != nil {
		RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	// parse the build script
	buildscript, err := script.ParseBuild(raw)
	if err != nil {
		// TODO if the YAML is invalid we should create a commit record
		// with an ERROR status so that the user knows why a build wasn't
		// triggered in the system
		RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	// save the commit to the database
	if err := database.SaveCommit(commit); err != nil {
		RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		return
	}

	// save the build to the database
	build := &Build{}
	build.Slug = "1" // TODO
	build.CommitID = commit.ID
	build.Created = time.Now().UTC()
	build.Status = "Pending"
	if err := database.SaveBuild(build); err != nil {
		RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		return
	}

	// notify websocket that a new build is pending
	// TODO we should, for consistency, just put this inside Queue.Add()
	queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript})

	// OK!
	RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
}
Example #6
0
func RepoCreateGithubLimited(w http.ResponseWriter, r *http.Request, u *User) error {
	teamName := r.FormValue("team")
	owner := r.FormValue("owner")
	name := r.FormValue("name")

	readToken := r.FormValue("read-token")
	writeToken := r.FormValue("write-token")

	u.GithubToken = readToken
	u.GithubWriteToken = writeToken

	// get the github settings from the database
	settings := database.SettingsMust()
	fmt.Printf("got settings: %s\n", settings)

	// create the GitHub client
	rClient := github.New(readToken)
	wClient := github.New(writeToken)
	rClient.ApiUrl = settings.GitHubApiUrl
	wClient.ApiUrl = settings.GitHubApiUrl

	githubRepo, err := rClient.Repos.Find(owner, name)
	if err != nil {
		fmt.Printf("err1, %s\n", err)
		return err
	}

	repo, err := NewGitHubRepo(settings.GitHubDomain, owner, name, githubRepo.Private)

	if err != nil {
		fmt.Printf("err2, %s\n", err)
		return err
	}

	repo.URL = fmt.Sprintf("https://%s@%s/%s/%s", readToken, settings.GitHubDomain, owner, name)

	repo.UserID = u.ID
	repo.Private = githubRepo.Private

	// if the user chose to assign to a team account
	// we need to retrieve the team, verify the user
	// has access, and then set the team id.
	if len(teamName) > 0 {
		team, err := database.GetTeamSlug(teamName)
		if err != nil {
			return fmt.Errorf("Unable to find Team %s.", teamName)
		}

		// user must be an admin member of the team
		if ok, _ := database.IsMemberAdmin(u.ID, team.ID); !ok {
			return fmt.Errorf("Invalid permission to access Team %s.", teamName)
		}
		repo.TeamID = team.ID
	}

	// create a hook so that we get notified when code                                                                                                                                                                                            // is pushed to the repository and can execute a build.
	link := fmt.Sprintf("%s://%s/hook/github.com?id=%s", settings.Scheme, settings.Domain, repo.Slug)

	// add the hook
	if _, err := wClient.Hooks.CreateUpdate(owner, name, link); err != nil {
		return fmt.Errorf("Unable to add Hook to your GitHub repository. %s", err.Error())
	}

	// Save to the database
	if err := database.SaveRepo(repo); err != nil {
		return fmt.Errorf("Error saving repository to the database. %s", err)
	}
	// Save user to the database
	if err := database.SaveUser(u); err != nil {
		return fmt.Errorf("Error saving user to the database. %s", err)
	}

	return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)

}