Skip to content

Commit

Permalink
feat: gerrit integration (#3899)
Browse files Browse the repository at this point in the history
  • Loading branch information
sguiheux authored and fsamin committed Apr 8, 2019
1 parent 6ec5102 commit 000dd3b
Show file tree
Hide file tree
Showing 90 changed files with 9,920 additions and 555 deletions.
2 changes: 2 additions & 0 deletions engine/api/api_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ func (api *API) InitRouter() {
r.Handle("/project/{permProjectKey}/repositories_manager", r.GET(api.getRepositoriesManagerForProjectHandler))
r.Handle("/project/{permProjectKey}/repositories_manager/{name}/authorize", r.POST(api.repositoriesManagerAuthorizeHandler))
r.Handle("/project/{permProjectKey}/repositories_manager/{name}/authorize/callback", r.POST(api.repositoriesManagerAuthorizeCallbackHandler))
r.Handle("/project/{permProjectKey}/repositories_manager/{name}/authorize/basicauth", r.POST(api.repositoriesManagerAuthorizeBasicHandler))
r.Handle("/project/{permProjectKey}/repositories_manager/{name}", r.DELETE(api.deleteRepositoriesManagerHandler))
r.Handle("/project/{permProjectKey}/repositories_manager/{name}/repo", r.GET(api.getRepoFromRepositoriesManagerHandler))
r.Handle("/project/{permProjectKey}/repositories_manager/{name}/repos", r.GET(api.getReposFromRepositoriesManagerHandler))
Expand All @@ -328,6 +329,7 @@ func (api *API) InitRouter() {

// config
r.Handle("/config/user", r.GET(api.ConfigUserHandler, Auth(false)))
r.Handle("/config/vcs", r.GET(api.ConfigVCShandler, NeedService()))

// Users
r.Handle("/user", r.GET(api.getUsersHandler))
Expand Down
12 changes: 12 additions & 0 deletions engine/api/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"net/http"

"github.com/ovh/cds/engine/api/repositoriesmanager"
"github.com/ovh/cds/engine/service"
"github.com/ovh/cds/sdk"
)
Expand All @@ -14,3 +15,14 @@ func (api *API) ConfigUserHandler() service.Handler {
return service.WriteJSON(w, map[string]string{sdk.ConfigURLAPIKey: api.Config.URL.API, sdk.ConfigURLUIKey: api.Config.URL.UI}, http.StatusOK)
}
}

// ConfigVCShandler return the configuration of vcs server
func (api *API) ConfigVCShandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
vcsServers, err := repositoriesmanager.LoadAll(ctx, api.mustDB(), api.Cache)
if err != nil {
return err
}
return service.WriteJSON(w, vcsServers, http.StatusOK)
}
}
32 changes: 32 additions & 0 deletions engine/api/event/publish_workflow_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,38 @@ func PublishWorkflowNodeRun(db gorp.SqlExecutor, nr sdk.WorkflowNodeRun, w sdk.W
}
}

// Try to get gerrit variable
var project, changeID, branch, revision, url string
projectParam := sdk.ParameterFind(&nr.BuildParameters, "git.repository")
if projectParam != nil {
project = projectParam.Value
}
changeIDParam := sdk.ParameterFind(&nr.BuildParameters, "gerrit.change.id")
if changeIDParam != nil {
changeID = changeIDParam.Value
}
branchParam := sdk.ParameterFind(&nr.BuildParameters, "gerrit.change.branch")
if branchParam != nil {
branch = branchParam.Value
}
revisionParams := sdk.ParameterFind(&nr.BuildParameters, "git.hash")
if revisionParams != nil {
revision = revisionParams.Value
}
urlParams := sdk.ParameterFind(&nr.BuildParameters, "cds.ui.pipeline.run")
if urlParams != nil {
url = urlParams.Value
}
if changeID != "" && project != "" && branch != "" && revision != "" {
e.GerritChange = &sdk.GerritChangeEvent{
ID: changeID,
DestBranch: branch,
Project: project,
Revision: revision,
URL: url,
}
}

e.NodeName = nodeName
var envName string
var appName string
Expand Down
1 change: 1 addition & 0 deletions engine/api/pipeline/pipeline_importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ func Import(db gorp.SqlExecutor, store cache.Store, proj *sdk.Project, pip *sdk.
msgChan <- sdk.NewMessage(sdk.MsgPipelineCreated, pip.Name)
}
}

//Reload the pipeline
pip2, err := LoadPipeline(db, proj.Key, pip.Name, false)
if err != nil {
Expand Down
81 changes: 79 additions & 2 deletions engine/api/repositories_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@ import (

func (api *API) getRepositoriesManagerHandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
rms, err := repositoriesmanager.LoadAll(ctx, api.mustDB(), api.Cache)
vcsServers, err := repositoriesmanager.LoadAll(ctx, api.mustDB(), api.Cache)
if err != nil {
return sdk.WrapError(err, "error")
}
rms := make([]string, 0, len(vcsServers))
for k := range vcsServers {
rms = append(rms, k)
}
return service.WriteJSON(w, rms, http.StatusOK)
}
}
Expand Down Expand Up @@ -69,7 +73,7 @@ func (api *API) repositoriesManagerAuthorizeHandler() service.Handler {
if err != nil {
return sdk.WrapError(sdk.ErrNoReposManagerAuth, "repositoriesManagerAuthorize> error with AuthorizeRedirect %s", err)
}
log.Info("repositoriesManagerAuthorize> [%s] RequestToken=%s; URL=%s", proj.Key, token, url)
log.Debug("repositoriesManagerAuthorize> [%s] RequestToken=%s; URL=%s", proj.Key, token, url)

data := map[string]string{
"project_key": proj.Key,
Expand All @@ -80,6 +84,12 @@ func (api *API) repositoriesManagerAuthorizeHandler() service.Handler {
"username": deprecatedGetUser(ctx).Username,
}

if token != "" {
data["auth_type"] = "oauth"
} else {
data["auth_type"] = "basic"
}

api.Cache.Set(cache.Key("reposmanager", "oauth", token), data)
return service.WriteJSON(w, data, http.StatusOK)
}
Expand Down Expand Up @@ -166,6 +176,73 @@ func (api *API) repositoriesManagerOAuthCallbackHandler() service.Handler {
}
}

func (api *API) repositoriesManagerAuthorizeBasicHandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
projectKey := vars["permProjectKey"]
rmName := vars["name"]

var tv map[string]interface{}
if err := service.UnmarshalBody(r, &tv); err != nil {
return err
}

var username, secret string
if tv["username"] != nil {
username = tv["username"].(string)
}
if tv["secret"] != nil {
secret = tv["secret"].(string)
}

if username == "" || secret == "" {
return sdk.WrapError(sdk.ErrWrongRequest, "cannot get token nor verifier from data")
}

proj, errP := project.Load(api.mustDB(), api.Cache, projectKey, deprecatedGetUser(ctx))
if errP != nil {
return sdk.WrapError(errP, "cannot load project %s", projectKey)
}

tx, errT := api.mustDB().Begin()
if errT != nil {
return sdk.WrapError(errT, "cannot start transaction")
}
defer tx.Rollback() // nolint

vcsServerForProject := &sdk.ProjectVCSServer{
Name: rmName,
Username: deprecatedGetUser(ctx).Username,
Data: map[string]string{
"token": username,
"secret": secret,
},
}

if err := repositoriesmanager.InsertForProject(tx, proj, vcsServerForProject); err != nil {
return sdk.WrapError(err, "unable to set repository manager data for project %s", projectKey)
}

client, err := repositoriesmanager.AuthorizedClient(ctx, tx, api.Cache, vcsServerForProject)
if err != nil {
return sdk.WrapError(sdk.ErrNoReposManagerClientAuth, "cannot get client for project %s: %v", proj.Key, err)
}

if _, err = client.Repos(ctx); err != nil {
return sdk.WrapError(err, "unable to connect %s to %s", proj.Key, rmName)
}

if err := tx.Commit(); err != nil {
return sdk.WrapError(err, "cannot commit transaction")
}

event.PublishAddVCSServer(proj, vcsServerForProject.Name, deprecatedGetUser(ctx))

return service.WriteJSON(w, proj, http.StatusOK)

}
}

func (api *API) repositoriesManagerAuthorizeCallbackHandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
Expand Down
34 changes: 17 additions & 17 deletions engine/api/repositoriesmanager/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,27 @@ func processEvent(ctx context.Context, db *gorp.DbMap, event sdk.Event, store ca
var c sdk.VCSAuthorizedClient
var errC error

if event.EventType == fmt.Sprintf("%T", sdk.EventRunWorkflowNode{}) {
var eventWNR sdk.EventRunWorkflowNode
if event.EventType != fmt.Sprintf("%T", sdk.EventRunWorkflowNode{}) {
return nil
}

if err := mapstructure.Decode(event.Payload, &eventWNR); err != nil {
return fmt.Errorf("repositoriesmanager>processEvent> Error during consumption: %v", err)
}
if eventWNR.RepositoryManagerName == "" {
return nil
}
vcsServer, err := LoadForProject(db, event.ProjectKey, eventWNR.RepositoryManagerName)
if err != nil {
return fmt.Errorf("repositoriesmanager>processEvent> AuthorizedClient (%s, %s) > err:%s", event.ProjectKey, eventWNR.RepositoryManagerName, err)
}
var eventWNR sdk.EventRunWorkflowNode

c, errC = AuthorizedClient(ctx, db, store, vcsServer)
if errC != nil {
return fmt.Errorf("repositoriesmanager>processEvent> AuthorizedClient (%s, %s) > err:%s", event.ProjectKey, eventWNR.RepositoryManagerName, errC)
}
} else {
if err := mapstructure.Decode(event.Payload, &eventWNR); err != nil {
return fmt.Errorf("repositoriesmanager>processEvent> Error during consumption: %v", err)
}
if eventWNR.RepositoryManagerName == "" {
return nil
}
vcsServer, err := LoadForProject(db, event.ProjectKey, eventWNR.RepositoryManagerName)
if err != nil {
return fmt.Errorf("repositoriesmanager>processEvent> AuthorizedClient (%s, %s) > err:%s", event.ProjectKey, eventWNR.RepositoryManagerName, err)
}

c, errC = AuthorizedClient(ctx, db, store, vcsServer)
if errC != nil {
return fmt.Errorf("repositoriesmanager>processEvent> AuthorizedClient (%s, %s) > err:%s", event.ProjectKey, eventWNR.RepositoryManagerName, errC)
}

if err := c.SetStatus(ctx, event); err != nil {
RetryEvent(&event, err, store)
Expand Down
38 changes: 24 additions & 14 deletions engine/api/repositoriesmanager/repositories_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,30 @@ import (
"github.com/ovh/cds/sdk/log"
)

func LoadByName(ctx context.Context, db gorp.SqlExecutor, vcsName string) (sdk.VCSConfiguration, error) {
var vcsServer sdk.VCSConfiguration
srvs, err := services.FindByType(db, services.TypeVCS)
if err != nil {
return vcsServer, sdk.WrapError(err, "Unable to load services")
}
if _, err := services.DoJSONRequest(ctx, srvs, "GET", fmt.Sprintf("/vcs/%s", vcsName), nil, &vcsServer); err != nil {
return vcsServer, sdk.WithStack(err)
}
return vcsServer, nil
}

//LoadAll Load all RepositoriesManager from the database
func LoadAll(ctx context.Context, db *gorp.DbMap, store cache.Store) ([]string, error) {
func LoadAll(ctx context.Context, db *gorp.DbMap, store cache.Store) (map[string]sdk.VCSConfiguration, error) {
srvs, err := services.FindByType(db, services.TypeVCS)
if err != nil {
return nil, sdk.WrapError(err, "Unable to load services")
}

vcsServers := map[string]interface{}{}
vcsServers := make(map[string]sdk.VCSConfiguration)
if _, err := services.DoJSONRequest(ctx, srvs, "GET", "/vcs", nil, &vcsServers); err != nil {
return nil, sdk.WithStack(err)
}
servers := []string{}
for k := range vcsServers {
servers = append(servers, k)
}
return servers, nil
return vcsServers, nil
}

type vcsConsumer struct {
Expand Down Expand Up @@ -304,17 +312,17 @@ func (c *vcsClient) Branch(ctx context.Context, fullname string, branchName stri
}

// DefaultBranch get default branch from given repository
func DefaultBranch(ctx context.Context, c sdk.VCSAuthorizedClient, fullname string) (string, error) {
func DefaultBranch(ctx context.Context, c sdk.VCSAuthorizedClient, fullname string) (sdk.VCSBranch, error) {
branches, err := c.Branches(ctx, fullname)
if err != nil {
return "", sdk.WrapError(err, "Unable to list branches on repository %s", fullname)
return sdk.VCSBranch{}, sdk.WrapError(err, "Unable to list branches on repository %s", fullname)
}
for _, b := range branches {
if b.Default {
return b.DisplayID, nil
return b, nil
}
}
return "", sdk.NewErrorFrom(sdk.ErrNotFound, "unable to find default branch on repository %s (among %d branches)", fullname, len(branches))
return sdk.VCSBranch{}, sdk.NewErrorFrom(sdk.ErrNotFound, "unable to find default branch on repository %s (among %d branches)", fullname, len(branches))
}

func (c *vcsClient) Commits(ctx context.Context, fullname, branch, since, until string) ([]sdk.VCSCommit, error) {
Expand Down Expand Up @@ -533,9 +541,11 @@ func (c *vcsClient) GrantReadPermission(ctx context.Context, repo string) error

// WebhooksInfos is a set of info about webhooks
type WebhooksInfos struct {
WebhooksSupported bool `json:"webhooks_supported"`
WebhooksDisabled bool `json:"webhooks_disabled"`
Icon string `json:"webhooks_icon"`
WebhooksSupported bool `json:"webhooks_supported"`
WebhooksDisabled bool `json:"webhooks_disabled"`
GerritHookDisabled bool `json:"gerrithook_disabled"`
Icon string `json:"webhooks_icon"`
Events []string `json:"events"`
}

// GetWebhooksInfos returns webhooks_supported, webhooks_disabled, webhooks_creation_supported, webhooks_creation_disabled for a vcs server
Expand Down
2 changes: 1 addition & 1 deletion engine/api/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func (api *API) getApplicationOverviewHandler() service.Handler {

// GET LAST BUILD
tagFilter := make(map[string]string, 1)
tagFilter["git.branch"] = defaultBranch
tagFilter["git.branch"] = defaultBranch.DisplayID
for _, w := range app.Usage.Workflows {
runs, _, _, _, errR := workflow.LoadRuns(db, key, w.Name, 0, 5, tagFilter)
if errR != nil {
Expand Down
6 changes: 3 additions & 3 deletions engine/api/workflow/dao_data_hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ func insertNodeHookData(db gorp.SqlExecutor, w *sdk.Workflow, n *sdk.Node) error
Configurable: false,
}

if model.Name == sdk.RepositoryWebHookModelName || model.Name == sdk.GitPollerModelName {
if model.Name == sdk.RepositoryWebHookModelName || model.Name == sdk.GitPollerModelName || model.Name == sdk.GerritHookModelName {
if n.Context.ApplicationID == 0 || w.Applications[n.Context.ApplicationID].RepositoryFullname == "" || w.Applications[n.Context.ApplicationID].VCSServer == "" {
return sdk.NewErrorFrom(sdk.ErrForbidden, "cannot create a git poller or repository webhook on an application without a repository")
}
h.Config["vcsServer"] = sdk.WorkflowNodeHookConfigValue{
h.Config[sdk.HookConfigVCSServer] = sdk.WorkflowNodeHookConfigValue{
Value: w.Applications[n.Context.ApplicationID].VCSServer,
Configurable: false,
}
h.Config["repoFullName"] = sdk.WorkflowNodeHookConfigValue{
h.Config[sdk.HookConfigRepoFullName] = sdk.WorkflowNodeHookConfigValue{
Value: w.Applications[n.Context.ApplicationID].RepositoryFullname,
Configurable: false,
}
Expand Down
4 changes: 2 additions & 2 deletions engine/api/workflow/dao_node_run_vulnerability.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ func HandleVulnerabilityReport(ctx context.Context, db gorp.SqlExecutor, cache c
return sdk.WrapError(sdk.ErrNoReposManagerClientAuth, "HandleVulnerabilityReport> Cannot get repo client %s : %v", nr.VCSServer, erra)
}

var errB error
defaultBranch, errB = repositoriesmanager.DefaultBranch(ctx, client, nr.VCSRepository)
b, errB := repositoriesmanager.DefaultBranch(ctx, client, nr.VCSRepository)
if errB != nil {
return sdk.WrapError(errB, "HandleVulnerabilityReport> Unable to get default branch")
}
defaultBranch = b.DisplayID
}

// Get report on the current node run if exist
Expand Down
Loading

0 comments on commit 000dd3b

Please sign in to comment.