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) }
// 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) }
// Processes a generic POST-RECEIVE Bitbucket hook and // attempts to trigger a build. func (h *BitbucketHandler) Hook(w http.ResponseWriter, r *http.Request) error { // get the payload from the request payload := r.FormValue("payload") // parse the post-commit hook hook, err := bitbucket.ParseHook([]byte(payload)) if err != nil { return err } // 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.Commits[len(hook.Commits)-1].Hash, repo.ID) if err != nil && err != sql.ErrNoRows { return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway) } commit := &Commit{} commit.RepoID = repo.ID commit.Branch = hook.Commits[len(hook.Commits)-1].Branch commit.Hash = hook.Commits[len(hook.Commits)-1].Hash commit.Status = "Pending" commit.Created = time.Now().UTC() commit.Message = hook.Commits[len(hook.Commits)-1].Message commit.Timestamp = time.Now().UTC().String() commit.SetAuthor(hook.Commits[len(hook.Commits)-1].Author) // get the github settings from the database settings := database.SettingsMust() // create the Bitbucket client client := bitbucket.New( settings.BitbucketKey, settings.BitbucketSecret, user.BitbucketToken, user.BitbucketSecret, ) // get the yaml from the database raw, err := client.Sources.Find(repo.Owner, repo.Name, commit.Hash, ".drone.yml") if err != nil { return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) } // parse the build script buildscript, err := script.ParseBuild([]byte(raw.Data), repo.Params) 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) } // send the build to the queue h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) // OK! return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) }
// Processes a generic POST-RECEIVE Gogs hook and // attempts to trigger a build. func (h *GogsHandler) Hook(w http.ResponseWriter, r *http.Request) error { defer r.Body.Close() payloadbytes, err := ioutil.ReadAll(r.Body) if err != nil { println(err.Error()) return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } fmt.Printf("body is => %s\n", string(payloadbytes)) payload, err := ParseHook(payloadbytes) if err != nil { println(err.Error()) return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } fmt.Printf("payload parsed\n") // Verify that the commit doesn't already exist. // We should never build the same commit twice. _, err = database.GetCommitHash(payload.Commits[0].Id, payload.Repo.Id) if err != nil && err != sql.ErrNoRows { return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway) } fmt.Printf("commit hash checked\n") // Save repo to the database if needed var urlParts = strings.Split(payload.Repo.Url, "/") repo, err := setupRepo(urlParts, payload) if err != nil { println(err.Error()) return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } commit := &Commit{} commit.RepoID = repo.ID commit.Branch = payload.Branch() commit.Hash = payload.Commits[0].Id commit.Status = "Pending" commit.Created = time.Now().UTC() commit.Message = payload.Commits[0].Message commit.Timestamp = time.Now().UTC().String() commit.SetAuthor(payload.Commits[0].Author.Name) fmt.Printf("commit struct created\n") // save the commit to the database if err := database.SaveCommit(commit); err != nil { return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } fmt.Printf("commit struct saved\n") var service_endpoint = urlParts[2] if os.Getenv("GOGS_URL") != "" { service_endpoint = os.Getenv("GOGS_URL") } // GET .drone.yml file var droneYmlUrl = fmt.Sprintf(droneYmlUrlPattern, service_endpoint, urlParts[3], urlParts[4], commit.Hash) println("droneYmlUrl is ", droneYmlUrl) ymlGetResponse, err := http.Get(droneYmlUrl) var buildYml = "" if err != nil { println(err.Error()) return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } else { defer ymlGetResponse.Body.Close() yml, err := ioutil.ReadAll(ymlGetResponse.Body) if err != nil { println(err.Error()) return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } buildYml = string(yml) println("yml from http get: ", buildYml) } // parse the build script var repoParams = map[string]string{} println("parsing yml") buildscript, err := script.ParseBuild([]byte(buildYml), repoParams) 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) } fmt.Printf("build script parsed\n") // 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) } println("build saved") // send the build to the queue h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) fmt.Printf("build task added to queue\n") // OK! return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) }
// execute will execute the build task and persist // the results to the datastore. func (w *worker) execute(task *BuildTask) error { // we need to be sure that we can recover // from any sort panic that could occur // to avoid brining down the entire application defer func() { if e := recover(); e != nil { task.Build.Finished = time.Now().UTC() task.Commit.Finished = time.Now().UTC() task.Build.Duration = task.Build.Finished.Unix() - task.Build.Started.Unix() task.Commit.Duration = task.Build.Finished.Unix() - task.Build.Started.Unix() task.Commit.Status = "Error" task.Build.Status = "Error" database.SaveBuild(task.Build) database.SaveCommit(task.Commit) } }() // update commit and build status task.Commit.Status = "Started" task.Build.Status = "Started" task.Build.Started = time.Now().UTC() task.Commit.Started = time.Now().UTC() // persist the commit to the database if err := database.SaveCommit(task.Commit); err != nil { return err } // persist the build to the database if err := database.SaveBuild(task.Build); err != nil { return err } // get settings settings, _ := database.GetSettings() // notification context context := ¬ify.Context{ Repo: task.Repo, Commit: task.Commit, Host: settings.URL().String(), } // parse the build script buildscript, err := script.ParseBuild([]byte(task.Build.BuildScript), task.Repo.Params) if err != nil { log.Printf("Could not parse your .drone.yml file. It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n") return err } // send all "started" notifications if buildscript.Notifications != nil { buildscript.Notifications.Send(context) } // Send "started" notification to Github if err := updateGitHubStatus(task.Repo, task.Commit); err != nil { log.Printf("error updating github status: %s\n", err.Error()) } // make sure a channel exists for the repository, // the commit, and the commit output (TODO) reposlug := fmt.Sprintf("%s/%s/%s", task.Repo.Host, task.Repo.Owner, task.Repo.Name) commitslug := fmt.Sprintf("%s/%s/%s/commit/%s/%s", task.Repo.Host, task.Repo.Owner, task.Repo.Name, task.Commit.Branch, task.Commit.Hash) consoleslug := fmt.Sprintf("%s/%s/%s/commit/%s/%s/builds/%s", task.Repo.Host, task.Repo.Owner, task.Repo.Name, task.Commit.Branch, task.Commit.Hash, task.Build.Slug) channel.Create(reposlug) channel.Create(commitslug) channel.CreateStream(consoleslug) // notify the channels that the commit and build started channel.SendJSON(reposlug, task.Commit) channel.SendJSON(commitslug, task.Build) var buf = &bufferWrapper{channel: consoleslug} // append private parameters to the environment // variable section of the .drone.yml file, iff // this is not a pull request (for security purposes) if task.Repo.Params != nil && len(task.Commit.PullRequest) == 0 { for k, v := range task.Repo.Params { buildscript.Env = append(buildscript.Env, k+"="+v) } } defer func() { // update the status of the commit using the // GitHub status API. if err := updateGitHubStatus(task.Repo, task.Commit); err != nil { log.Printf("error updating github status: %s\n", err.Error()) } }() // execute the build passed, buildErr := w.runBuild(task, buildscript, buf) task.Build.Finished = time.Now().UTC() task.Commit.Finished = time.Now().UTC() task.Build.Duration = task.Build.Finished.UnixNano() - task.Build.Started.UnixNano() task.Commit.Duration = task.Build.Finished.UnixNano() - task.Build.Started.UnixNano() task.Commit.Status = "Success" task.Build.Status = "Success" task.Build.Stdout = buf.buf.String() // if exit code != 0 set to failure if passed { task.Commit.Status = "Failure" task.Build.Status = "Failure" if buildErr != nil && task.Build.Stdout == "" { // TODO: If you wanted to have very friendly error messages, you could do that here task.Build.Stdout = buildErr.Error() + "\n" } } // persist the build to the database if err := database.SaveBuild(task.Build); err != nil { return err } // persist the commit to the database if err := database.SaveCommit(task.Commit); err != nil { return err } // notify the channels that the commit and build finished channel.SendJSON(reposlug, task.Commit) channel.SendJSON(commitslug, task.Build) channel.Close(consoleslug) // send all "finished" notifications if buildscript.Notifications != nil { buildscript.Notifications.Send(context) } return nil }
func (g *GitlabHandler) PullRequestHook(p *gogitlab.HookPayload, repo *Repo, user *User) error { obj := p.ObjectAttributes // Gitlab may trigger multiple hooks upon updating merge requests status // only build when it was just opened and the merge hasn't been checked yet. if !(obj.State == "opened" && obj.MergeStatus == "unchecked") { fmt.Println("Ignore GitLab Merge Requests") return nil } settings := database.SettingsMust() client := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, user.GitlabToken) // GitLab merge-requests hook doesn't include repository data. // Have to fetch it manually src, err := client.RepoBranch(strconv.Itoa(obj.SourceProjectId), obj.SourceBranch) if err != nil { return err } _, err = database.GetCommitHash(src.Commit.Id, repo.ID) if err != nil && err != sql.ErrNoRows { fmt.Println("commit already exists") return err } commit := &Commit{} commit.RepoID = repo.ID commit.Branch = src.Name commit.Hash = src.Commit.Id commit.Status = "Pending" commit.Created = time.Now().UTC() commit.PullRequest = strconv.Itoa(obj.IId) commit.Message = src.Commit.Message commit.Timestamp = src.Commit.AuthoredDateRaw commit.SetAuthor(src.Commit.Author.Email) content, err := client.RepoRawFile(strconv.Itoa(obj.SourceProjectId), commit.Hash, ".drone.yml") 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 fmt.Errorf("Failed to save build: %q", err) } return fmt.Errorf("Error to fetch build script: %q", err) } // parse the build script buildscript, err := script.ParseBuild(content, repo.Params) 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 fmt.Errorf("Failed to save build: %q", err) } return fmt.Errorf("Failed to parse build script: %q", err) } // save the commit to the database if err := database.SaveCommit(commit); err != nil { return fmt.Errorf("Failed to save commit: %q", err) } // 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 fmt.Errorf("Failed to save build: %q", err) } g.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) return nil }
func (g *GitlabHandler) Hook(w http.ResponseWriter, r *http.Request) error { rID := r.FormValue("id") repo, err := database.GetRepoSlug(rID) if err != nil { return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) } user, err := database.GetUser(repo.UserID) if err != nil { return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) } payload, _ := ioutil.ReadAll(r.Body) parsed, err := gogitlab.ParseHook(payload) if err != nil { return err } if parsed.ObjectKind == "merge_request" { fmt.Println(string(payload)) if err := g.PullRequestHook(parsed, repo, user); err != nil { return err } return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) } if len(parsed.After) == 0 { return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) } _, err = database.GetCommitHash(parsed.After, repo.ID) if err != nil && err != sql.ErrNoRows { fmt.Println("commit already exists") return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway) } commit := &Commit{} commit.RepoID = repo.ID commit.Branch = parsed.Branch() commit.Hash = parsed.After commit.Status = "Pending" commit.Created = time.Now().UTC() head := parsed.Head() commit.Message = head.Message commit.Timestamp = head.Timestamp if head.Author != nil { commit.SetAuthor(head.Author.Email) } else { commit.SetAuthor(parsed.UserName) } // get the github settings from the database settings := database.SettingsMust() // get the drone.yml file from GitHub client := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, user.GitlabToken) content, err := client.RepoRawFile(ns(repo.Owner, repo.Name), commit.Hash, ".drone.yml") 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) } // parse the build script buildscript, err := script.ParseBuild(content, repo.Params) 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) } g.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) // OK! return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) }