github.com/unclejack/drone@v0.2.1-0.20140918182345-831b034aa33b/pkg/handler/gitlab.go (about)

     1  package handler
     2  
     3  import (
     4  	"database/sql"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/drone/drone/pkg/database"
    13  	. "github.com/drone/drone/pkg/model"
    14  	"github.com/drone/drone/pkg/queue"
    15  	"github.com/plouc/go-gitlab-client"
    16  )
    17  
    18  type GitlabHandler struct {
    19  	queue   *queue.Queue
    20  	apiPath string
    21  }
    22  
    23  func NewGitlabHandler(queue *queue.Queue) *GitlabHandler {
    24  	return &GitlabHandler{
    25  		queue:   queue,
    26  		apiPath: "/api/v3",
    27  	}
    28  }
    29  
    30  func (g *GitlabHandler) Add(w http.ResponseWriter, r *http.Request, u *User) error {
    31  	settings := database.SettingsMust()
    32  	teams, err := database.ListTeams(u.ID)
    33  	if err != nil {
    34  		return err
    35  	}
    36  	data := struct {
    37  		User     *User
    38  		Teams    []*Team
    39  		Settings *Settings
    40  	}{u, teams, settings}
    41  	// if the user hasn't linked their GitLab account
    42  	// render a different template
    43  	if len(u.GitlabToken) == 0 {
    44  		return RenderTemplate(w, "gitlab_link.html", &data)
    45  	}
    46  	// otherwise display the template for adding
    47  	// a new GitLab repository.
    48  	return RenderTemplate(w, "gitlab_add.html", &data)
    49  }
    50  
    51  func (g *GitlabHandler) Link(w http.ResponseWriter, r *http.Request, u *User) error {
    52  	token := strings.TrimSpace(r.FormValue("token"))
    53  
    54  	if len(u.GitlabToken) == 0 || token != u.GitlabToken && len(token) > 0 {
    55  		u.GitlabToken = token
    56  		settings := database.SettingsMust()
    57  		gl := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, u.GitlabToken)
    58  		_, err := gl.CurrentUser()
    59  		if err != nil {
    60  			return fmt.Errorf("Private Token is not valid: %q", err)
    61  		}
    62  		if err := database.SaveUser(u); err != nil {
    63  			return RenderError(w, err, http.StatusBadRequest)
    64  		}
    65  	}
    66  
    67  	http.Redirect(w, r, "/new/gitlab", http.StatusSeeOther)
    68  	return nil
    69  }
    70  
    71  func (g *GitlabHandler) ReLink(w http.ResponseWriter, r *http.Request, u *User) error {
    72  	data := struct {
    73  		User *User
    74  	}{u}
    75  	return RenderTemplate(w, "gitlab_link.html", &data)
    76  }
    77  
    78  func (g *GitlabHandler) Create(w http.ResponseWriter, r *http.Request, u *User) error {
    79  	teamName := r.FormValue("team")
    80  	owner := r.FormValue("owner")
    81  	name := r.FormValue("name")
    82  
    83  	repo, err := g.newGitlabRepo(u, owner, name)
    84  	if err != nil {
    85  		return err
    86  	}
    87  
    88  	if len(teamName) > 0 {
    89  		team, err := database.GetTeamSlug(teamName)
    90  		if err != nil {
    91  			return fmt.Errorf("Unable to find Team %s.", teamName)
    92  		}
    93  
    94  		// user must be an admin member of the team
    95  		if ok, _ := database.IsMemberAdmin(u.ID, team.ID); !ok {
    96  			return fmt.Errorf("Invalid permission to access Team %s.", teamName)
    97  		}
    98  		repo.TeamID = team.ID
    99  	}
   100  
   101  	// Save to the database
   102  	if err := database.SaveRepo(repo); err != nil {
   103  		return fmt.Errorf("Error saving repository to the database. %s", err)
   104  	}
   105  
   106  	return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
   107  }
   108  
   109  func (g *GitlabHandler) newGitlabRepo(u *User, owner, name string) (*Repo, error) {
   110  	settings := database.SettingsMust()
   111  	gl := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, u.GitlabToken)
   112  
   113  	project, err := gl.Project(ns(owner, name))
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	var cloneUrl string
   119  	if project.Public {
   120  		cloneUrl = project.HttpRepoUrl
   121  	} else {
   122  		cloneUrl = project.SshRepoUrl
   123  	}
   124  
   125  	repo, err := NewRepo(settings.GitlabDomain, owner, name, ScmGit, cloneUrl)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	repo.UserID = u.ID
   131  	repo.Private = !project.Public
   132  	if repo.Private {
   133  		// name the key
   134  		keyName := fmt.Sprintf("%s@%s", repo.Owner, settings.Domain)
   135  
   136  		// TODO: (fudanchii) check if we already opted to use UserKey
   137  
   138  		// create the github key, or update if one already exists
   139  		if err := gl.AddProjectDeployKey(ns(owner, name), keyName, repo.PublicKey); err != nil {
   140  			return nil, fmt.Errorf("Unable to add Public Key to your GitLab repository.")
   141  		}
   142  	}
   143  
   144  	link := fmt.Sprintf("%s://%s/hook/gitlab?id=%s", settings.Scheme, settings.Domain, repo.Slug)
   145  	if err := gl.AddProjectHook(ns(owner, name), link, true, false, true); err != nil {
   146  		return nil, fmt.Errorf("Unable to add Hook to your GitLab repository.")
   147  	}
   148  
   149  	return repo, err
   150  }
   151  
   152  func (g *GitlabHandler) Hook(w http.ResponseWriter, r *http.Request) error {
   153  	rID := r.FormValue("id")
   154  	repo, err := database.GetRepoSlug(rID)
   155  	if err != nil {
   156  		return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
   157  	}
   158  
   159  	user, err := database.GetUser(repo.UserID)
   160  	if err != nil {
   161  		return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
   162  	}
   163  
   164  	payload, _ := ioutil.ReadAll(r.Body)
   165  	parsed, err := gogitlab.ParseHook(payload)
   166  	if err != nil {
   167  		return err
   168  	}
   169  	if parsed.ObjectKind == "merge_request" {
   170  		fmt.Println(string(payload))
   171  		if err := g.PullRequestHook(parsed, repo, user); err != nil {
   172  			return err
   173  		}
   174  		return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
   175  	}
   176  
   177  	if len(parsed.After) == 0 {
   178  		return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
   179  	}
   180  
   181  	_, err = database.GetCommitHash(parsed.After, repo.ID)
   182  	if err != nil && err != sql.ErrNoRows {
   183  		fmt.Println("commit already exists")
   184  		return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway)
   185  	}
   186  
   187  	commit := &Commit{}
   188  	commit.RepoID = repo.ID
   189  	commit.Branch = parsed.Branch()
   190  	commit.Hash = parsed.After
   191  	commit.Status = "Pending"
   192  	commit.Created = time.Now().UTC()
   193  
   194  	head := parsed.Head()
   195  	commit.Message = head.Message
   196  	commit.Timestamp = head.Timestamp
   197  	if head.Author != nil {
   198  		commit.SetAuthor(head.Author.Email)
   199  	} else {
   200  		commit.SetAuthor(parsed.UserName)
   201  	}
   202  
   203  	// get the github settings from the database
   204  	settings := database.SettingsMust()
   205  
   206  	// get the drone.yml file from GitHub
   207  	client := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, user.GitlabToken)
   208  
   209  	buildscript, err := client.RepoRawFile(ns(repo.Owner, repo.Name), commit.Hash, ".drone.yml")
   210  	if err != nil {
   211  		msg := "No .drone.yml was found in this repository.  You need to add one.\n"
   212  		if err := saveFailedBuild(commit, msg); err != nil {
   213  			return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
   214  		}
   215  		return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
   216  	}
   217  
   218  	// save the commit to the database
   219  	if err := database.SaveCommit(commit); err != nil {
   220  		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
   221  	}
   222  
   223  	// save the build to the database
   224  	build := &Build{}
   225  	build.Slug = "1" // TODO
   226  	build.CommitID = commit.ID
   227  	build.Created = time.Now().UTC()
   228  	build.Status = "Pending"
   229  	build.BuildScript = string(buildscript)
   230  	if err := database.SaveBuild(build); err != nil {
   231  		return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
   232  	}
   233  
   234  	g.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build})
   235  
   236  	// OK!
   237  	return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK)
   238  
   239  }
   240  
   241  func (g *GitlabHandler) PullRequestHook(p *gogitlab.HookPayload, repo *Repo, user *User) error {
   242  	obj := p.ObjectAttributes
   243  
   244  	// Gitlab may trigger multiple hooks upon updating merge requests status
   245  	// only build when it was just opened and the merge hasn't been checked yet.
   246  	if !(obj.State == "opened" && obj.MergeStatus == "unchecked") {
   247  		fmt.Println("Ignore GitLab Merge Requests")
   248  		return nil
   249  	}
   250  
   251  	settings := database.SettingsMust()
   252  
   253  	client := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, user.GitlabToken)
   254  
   255  	// GitLab merge-requests hook doesn't include repository data.
   256  	// Have to fetch it manually
   257  	src, err := client.RepoBranch(strconv.Itoa(obj.SourceProjectId), obj.SourceBranch)
   258  	if err != nil {
   259  		return err
   260  	}
   261  
   262  	_, err = database.GetCommitHash(src.Commit.Id, repo.ID)
   263  	if err != nil && err != sql.ErrNoRows {
   264  		fmt.Println("commit already exists")
   265  		return err
   266  	}
   267  
   268  	commit := &Commit{}
   269  	commit.RepoID = repo.ID
   270  	commit.Branch = src.Name
   271  	commit.Hash = src.Commit.Id
   272  	commit.Status = "Pending"
   273  	commit.Created = time.Now().UTC()
   274  	commit.PullRequest = strconv.Itoa(obj.IId)
   275  
   276  	commit.Message = src.Commit.Message
   277  	commit.Timestamp = src.Commit.AuthoredDateRaw
   278  	commit.SetAuthor(src.Commit.Author.Email)
   279  
   280  	buildscript, err := client.RepoRawFile(strconv.Itoa(obj.SourceProjectId), commit.Hash, ".drone.yml")
   281  	if err != nil {
   282  		msg := "No .drone.yml was found in this repository.  You need to add one.\n"
   283  		if err := saveFailedBuild(commit, msg); err != nil {
   284  			return fmt.Errorf("Failed to save build: %q", err)
   285  		}
   286  		return fmt.Errorf("Error to fetch build script: %q", err)
   287  	}
   288  
   289  	// save the commit to the database
   290  	if err := database.SaveCommit(commit); err != nil {
   291  		return fmt.Errorf("Failed to save commit: %q", err)
   292  	}
   293  
   294  	// save the build to the database
   295  	build := &Build{}
   296  	build.Slug = "1" // TODO
   297  	build.CommitID = commit.ID
   298  	build.Created = time.Now().UTC()
   299  	build.Status = "Pending"
   300  	build.BuildScript = string(buildscript)
   301  	if err := database.SaveBuild(build); err != nil {
   302  		return fmt.Errorf("Failed to save build: %q", err)
   303  	}
   304  
   305  	g.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build})
   306  
   307  	return nil
   308  }
   309  
   310  // ns namespaces user and repo.
   311  // Returns user%2Frepo
   312  func ns(user, repo string) string {
   313  	return fmt.Sprintf("%s%%2F%s", user, repo)
   314  }