github.com/kvattikuti/drone@v0.2.1-0.20140603034306-d400229a327a/pkg/handler/gogs.go (about) 1 package handler 2 3 import ( 4 "database/sql" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "github.com/drone/drone/pkg/build/script" 9 "github.com/drone/drone/pkg/database" 10 . "github.com/drone/drone/pkg/model" 11 "github.com/drone/drone/pkg/queue" 12 "io/ioutil" 13 "net/http" 14 "os" 15 "strings" 16 "time" 17 ) 18 19 const ( 20 droneYmlUrlPattern = "http://%s/%s/%s/raw/%s/.drone.yml" 21 ) 22 23 type GogsHandler struct { 24 queue *queue.Queue 25 } 26 27 func NewGogsHandler(queue *queue.Queue) *GogsHandler { 28 return &GogsHandler{ 29 queue: queue, 30 } 31 } 32 33 // Processes a generic POST-RECEIVE Gogs hook and 34 // attempts to trigger a build. 35 func (h *GogsHandler) Hook(w http.ResponseWriter, r *http.Request) error { 36 37 defer r.Body.Close() 38 payloadbytes, err := ioutil.ReadAll(r.Body) 39 if err != nil { 40 println(err.Error()) 41 return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 42 } 43 fmt.Printf("body is => %s\n", string(payloadbytes)) 44 45 payload, err := ParseHook(payloadbytes) 46 if err != nil { 47 println(err.Error()) 48 return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 49 } 50 fmt.Printf("payload parsed\n") 51 52 // Verify that the commit doesn't already exist. 53 // We should never build the same commit twice. 54 _, err = database.GetCommitHash(payload.Commits[0].Id, payload.Repo.Id) 55 if err != nil && err != sql.ErrNoRows { 56 return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway) 57 } 58 fmt.Printf("commit hash checked\n") 59 60 // Save repo to the database if needed 61 var urlParts = strings.Split(payload.Repo.Url, "/") 62 63 repo, err := setupRepo(urlParts, payload) 64 if err != nil { 65 println(err.Error()) 66 return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 67 } 68 69 commit := &Commit{} 70 commit.RepoID = repo.ID 71 commit.Branch = payload.Branch() 72 commit.Hash = payload.Commits[0].Id 73 commit.Status = "Pending" 74 commit.Created = time.Now().UTC() 75 76 commit.Message = payload.Commits[0].Message 77 commit.Timestamp = time.Now().UTC().String() 78 commit.SetAuthor(payload.Commits[0].Author.Name) 79 fmt.Printf("commit struct created\n") 80 81 // save the commit to the database 82 if err := database.SaveCommit(commit); err != nil { 83 return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 84 } 85 fmt.Printf("commit struct saved\n") 86 87 var service_endpoint = urlParts[2] 88 if os.Getenv("GOGS_URL") != "" { 89 service_endpoint = os.Getenv("GOGS_URL") 90 } 91 // GET .drone.yml file 92 var droneYmlUrl = fmt.Sprintf(droneYmlUrlPattern, service_endpoint, urlParts[3], urlParts[4], commit.Hash) 93 println("droneYmlUrl is ", droneYmlUrl) 94 ymlGetResponse, err := http.Get(droneYmlUrl) 95 var buildYml = "" 96 if err != nil { 97 println(err.Error()) 98 return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 99 } else { 100 defer ymlGetResponse.Body.Close() 101 yml, err := ioutil.ReadAll(ymlGetResponse.Body) 102 if err != nil { 103 println(err.Error()) 104 return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 105 } 106 buildYml = string(yml) 107 println("yml from http get: ", buildYml) 108 } 109 110 // parse the build script 111 var repoParams = map[string]string{} 112 println("parsing yml") 113 buildscript, err := script.ParseBuild([]byte(buildYml), repoParams) 114 if err != nil { 115 msg := "Could not parse your .drone.yml file. It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n" 116 if err := saveFailedBuild(commit, msg); err != nil { 117 return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 118 } 119 return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 120 } 121 fmt.Printf("build script parsed\n") 122 123 // save the build to the database 124 build := &Build{} 125 build.Slug = "1" // TODO 126 build.CommitID = commit.ID 127 build.Created = time.Now().UTC() 128 build.Status = "Pending" 129 if err := database.SaveBuild(build); err != nil { 130 return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 131 } 132 println("build saved") 133 134 // send the build to the queue 135 h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) 136 fmt.Printf("build task added to queue\n") 137 138 // OK! 139 return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) 140 } 141 142 type PayloadAuthor struct { 143 Name string `json:"name"` 144 Email string `json:"email"` 145 } 146 147 type PayloadCommit struct { 148 Id string `json:"id"` 149 Message string `json:"message"` 150 Url string `json:"url"` 151 Author *PayloadAuthor `json:"author"` 152 } 153 154 type PayloadRepo struct { 155 Id int64 `json:"id"` 156 Name string `json:"name"` 157 Url string `json:"url"` 158 Description string `json:"description"` 159 Website string `json:"website"` 160 Watchers int `json:"watchers"` 161 Owner *PayloadAuthor `json:"author"` 162 Private bool `json:"private"` 163 } 164 165 // Payload represents payload information of payload. 166 type Payload struct { 167 Secret string `json:"secret"` 168 Ref string `json:"ref"` 169 Commits []*PayloadCommit `json:"commits"` 170 Repo *PayloadRepo `json:"repository"` 171 Pusher *PayloadAuthor `json:"pusher"` 172 } 173 174 var ErrInvalidReceiveHook = errors.New("Invalid JSON payload received over webhook") 175 176 func ParseHook(raw []byte) (*Payload, error) { 177 178 hook := Payload{} 179 if err := json.Unmarshal(raw, &hook); err != nil { 180 return nil, err 181 } 182 183 // it is possible the JSON was parsed, however, 184 // was not from Github (maybe was from Bitbucket) 185 // So we'll check to be sure certain key fields 186 // were populated 187 switch { 188 case hook.Repo == nil: 189 return nil, ErrInvalidReceiveHook 190 case len(hook.Ref) == 0: 191 return nil, ErrInvalidReceiveHook 192 } 193 194 return &hook, nil 195 } 196 197 func (h *Payload) Branch() string { 198 return strings.Replace(h.Ref, "refs/heads/", "", -1) 199 } 200 201 func setupRepo(urlParts []string, payload *Payload) (*Repo, error) { 202 println("urlParts: ", urlParts) 203 repo, err := database.GetRepoSlug(fmt.Sprintf("%s/%s/%s", urlParts[2], urlParts[3], urlParts[4])) 204 if err != nil { 205 if err != sql.ErrNoRows { 206 return nil, fmt.Errorf("error fetching repo: %s", err) 207 } 208 fmt.Errorf("Repo does not exist in database. %s", err) 209 210 // urlParts[2] will stay as-is (git.interior.vesseler as it used in url) 211 // need to modify payload.Repo.Url so that git clone works 212 repo_url := payload.Repo.Url 213 if os.Getenv("GOGS_URL") != "" { 214 repo_url = fmt.Sprintf("http://%s/%s/%s", os.Getenv("GOGS_URL"), urlParts[3], urlParts[4]) 215 } 216 217 repo, err = NewRepo(urlParts[2], urlParts[3], urlParts[4], ScmGit, repo_url) 218 if err != nil { 219 println(err.Error()) 220 return nil, fmt.Errorf("Repo object could not be created %s", err) 221 } 222 fmt.Printf("repo struct created\n") 223 user, err := database.GetUserEmail(payload.Repo.Owner.Email) 224 if err != nil { 225 return repo, fmt.Errorf("Repo could not find user with email %s, err= %s", payload.Repo.Owner.Email, err) 226 } 227 repo.UserID = user.ID 228 repo.Private = payload.Repo.Private 229 230 err = database.SaveRepo(repo) 231 if err != nil { 232 return repo, fmt.Errorf("Repo could not be saved to database. %s", err) 233 } else { 234 fmt.Printf("repo saved in database\n") 235 return repo, nil 236 } 237 } 238 fmt.Printf("repo exists in database\n") 239 return repo, nil 240 }