// UploadChkList builds the list of file checksums and whether or not they need // to be uploaded. If they do, the upload URL is also provided. func (s *Sandbox) UploadChkList() map[string]map[string]interface{} { /* Uh... */ chksumStats := make(map[string]map[string]interface{}) for _, chk := range s.Checksums { chksumStats[chk] = make(map[string]interface{}) k, _ := filestore.Get(chk) if k != nil { chksumStats[chk]["needs_upload"] = false } else { itemURL := fmt.Sprintf("/file_store/%s", chk) chksumStats[chk]["url"] = util.CustomURL(itemURL) chksumStats[chk]["needs_upload"] = true } } return chksumStats }
func formatPartials(results []map[string]interface{}, objs []indexer.Indexable, partialData map[string]interface{}) ([]map[string]interface{}, error) { var err error results, err = partialSearchFormat(results, partialData) if err != nil { return nil, err } for x, z := range results { tmpRes := make(map[string]interface{}) switch ro := objs[x].(type) { case *databag.DataBagItem: dbiURL := fmt.Sprintf("/data/%s/%s", ro.DataBagName, ro.RawData["id"].(string)) tmpRes["url"] = util.CustomURL(dbiURL) default: tmpRes["url"] = util.ObjURL(objs[x].(util.GoiardiObj)) } tmpRes["data"] = z results[x] = tmpRes } return results, nil }
func dataHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") pathArray := splitPath(r.URL.Path) dbResponse := make(map[string]interface{}) opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } if len(pathArray) == 1 { /* Either a list of data bags, or a POST to create a new one */ switch r.Method { case "GET": if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } /* The list */ dbList := databag.GetList() for _, k := range dbList { dbResponse[k] = util.CustomURL(fmt.Sprintf("/data/%s", k)) } case "POST": if !opUser.IsAdmin() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } dbData, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return } /* check that the name exists */ switch t := dbData["name"].(type) { case string: if t == "" { jsonErrorReport(w, r, "Field 'name' missing", http.StatusBadRequest) return } default: jsonErrorReport(w, r, "Field 'name' missing", http.StatusBadRequest) return } chefDbag, _ := databag.Get(dbData["name"].(string)) if chefDbag != nil { httperr := fmt.Errorf("Data bag %s already exists.", dbData["name"].(string)) jsonErrorReport(w, r, httperr.Error(), http.StatusConflict) return } chefDbag, nerr := databag.New(dbData["name"].(string)) if nerr != nil { jsonErrorReport(w, r, nerr.Error(), nerr.Status()) return } serr := chefDbag.Save() if serr != nil { jsonErrorReport(w, r, serr.Error(), http.StatusInternalServerError) return } if lerr := loginfo.LogEvent(opUser, chefDbag, "create"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } dbResponse["uri"] = util.ObjURL(chefDbag) w.WriteHeader(http.StatusCreated) default: /* The chef-pedant spec wants this response for * some reason. Mix it up, I guess. */ w.Header().Set("Allow", "GET, POST") jsonErrorReport(w, r, "GET, POST", http.StatusMethodNotAllowed) return } } else { dbName := pathArray[1] /* chef-pedant is unhappy about not reporting the HTTP status * as 404 by fetching the data bag before we see if the method * is allowed, so do a quick check for that here. */ if (len(pathArray) == 2 && r.Method == "PUT") || (len(pathArray) == 3 && r.Method == "POST") { var allowed string if len(pathArray) == 2 { allowed = "GET, POST, DELETE" } else { allowed = "GET, PUT, DELETE" } w.Header().Set("Allow", allowed) jsonErrorReport(w, r, "Method not allowed", http.StatusMethodNotAllowed) return } if opUser.IsValidator() || (!opUser.IsAdmin() && r.Method != "GET") { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } chefDbag, err := databag.Get(dbName) if err != nil { var errMsg string status := err.Status() if r.Method == "POST" { /* Posts get a special snowflake message */ errMsg = fmt.Sprintf("No data bag '%s' could be found. Please create this data bag before adding items to it.", dbName) } else { if len(pathArray) == 3 { /* This is nuts. */ if r.Method == "DELETE" { errMsg = fmt.Sprintf("Cannot load data bag %s item %s", dbName, pathArray[2]) } else { errMsg = fmt.Sprintf("Cannot load data bag item %s for data bag %s", pathArray[2], dbName) } } else { errMsg = err.Error() } } jsonErrorReport(w, r, errMsg, status) return } if len(pathArray) == 2 { /* getting list of data bag items and creating data bag * items. */ switch r.Method { case "GET": for _, k := range chefDbag.ListDBItems() { dbResponse[k] = util.CustomObjURL(chefDbag, k) } case "DELETE": /* The chef API docs don't say anything * about this existing, but it does, * and without it you can't delete data * bags at all. */ dbResponse["chef_type"] = "data_bag" dbResponse["json_class"] = "Chef::DataBag" dbResponse["name"] = chefDbag.Name err := chefDbag.Delete() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } if lerr := loginfo.LogEvent(opUser, chefDbag, "delete"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } case "POST": rawData := databag.RawDataBagJSON(r.Body) dbitem, nerr := chefDbag.NewDBItem(rawData) if nerr != nil { jsonErrorReport(w, r, nerr.Error(), nerr.Status()) return } if lerr := loginfo.LogEvent(opUser, dbitem, "create"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } /* The data bag return values are all * kinds of weird. Sometimes it sends * just the raw data, sometimes it sends * the whole object, sometimes a special * snowflake version. Ugh. Have to loop * through to avoid updating the pointer * in the cache by just assigning * dbitem.RawData to dbResponse. Urk. */ for k, v := range dbitem.RawData { dbResponse[k] = v } dbResponse["data_bag"] = dbitem.DataBagName dbResponse["chef_type"] = dbitem.ChefType w.WriteHeader(http.StatusCreated) default: w.Header().Set("Allow", "GET, DELETE, POST") jsonErrorReport(w, r, "GET, DELETE, POST", http.StatusMethodNotAllowed) return } } else { /* getting, editing, and deleting existing data bag items. */ dbItemName := pathArray[2] if _, err := chefDbag.GetDBItem(dbItemName); err != nil { var httperr string if r.Method != "DELETE" { httperr = fmt.Sprintf("Cannot load data bag item %s for data bag %s", dbItemName, chefDbag.Name) } else { httperr = fmt.Sprintf("Cannot load data bag %s item %s", chefDbag.Name, dbItemName) } jsonErrorReport(w, r, httperr, http.StatusNotFound) return } switch r.Method { case "GET": dbi, err := chefDbag.GetDBItem(dbItemName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } dbResponse = dbi.RawData case "DELETE": dbi, err := chefDbag.GetDBItem(dbItemName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } /* Gotta short circuit this */ enc := json.NewEncoder(w) if err := enc.Encode(&dbi); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } err = chefDbag.DeleteDBItem(dbItemName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } if lerr := loginfo.LogEvent(opUser, dbi, "delete"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } return case "PUT": rawData := databag.RawDataBagJSON(r.Body) if rawID, ok := rawData["id"]; ok { switch rawID := rawID.(type) { case string: if rawID != dbItemName { jsonErrorReport(w, r, "DataBagItem name mismatch.", http.StatusBadRequest) return } default: jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } } dbitem, err := chefDbag.UpdateDBItem(dbItemName, rawData) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } if lerr := loginfo.LogEvent(opUser, dbitem, "modify"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } /* Another weird data bag item response * which isn't at all unusual. */ for k, v := range dbitem.RawData { dbResponse[k] = v } dbResponse["data_bag"] = dbitem.DataBagName dbResponse["chef_type"] = dbitem.ChefType dbResponse["id"] = dbItemName default: w.Header().Set("Allow", "GET, DELETE, PUT") jsonErrorReport(w, r, "GET, DELETE, PUT", http.StatusMethodNotAllowed) return } } } enc := json.NewEncoder(w) if err := enc.Encode(&dbResponse); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } }
func environmentHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") accErr := checkAccept(w, r, "application/json") if accErr != nil { jsonErrorReport(w, r, accErr.Error(), http.StatusNotAcceptable) return } opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } pathArray := splitPath(r.URL.Path) envResponse := make(map[string]interface{}) var numResults string r.ParseForm() if nrs, found := r.Form["num_versions"]; found { if len(nrs) < 0 { jsonErrorReport(w, r, "invalid num_versions", http.StatusBadRequest) return } numResults = nrs[0] err := util.ValidateNumVersions(numResults) if err != nil { jsonErrorReport(w, r, "You have requested an invalid number of versions (x >= 0 || 'all')", err.Status()) return } } pathArrayLen := len(pathArray) if pathArrayLen == 1 { switch r.Method { case "GET": if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } envList := environment.GetList() for _, env := range envList { envResponse[env] = util.CustomURL(fmt.Sprintf("/environments/%s", env)) } case "POST": if !opUser.IsAdmin() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } envData, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return } if _, ok := envData["name"].(string); !ok || envData["name"].(string) == "" { jsonErrorReport(w, r, "Environment name missing", http.StatusBadRequest) return } chefEnv, _ := environment.Get(envData["name"].(string)) if chefEnv != nil { httperr := fmt.Errorf("Environment already exists") jsonErrorReport(w, r, httperr.Error(), http.StatusConflict) return } var eerr util.Gerror chefEnv, eerr = environment.NewFromJSON(envData) if eerr != nil { jsonErrorReport(w, r, eerr.Error(), eerr.Status()) return } if err := chefEnv.Save(); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusBadRequest) return } if lerr := loginfo.LogEvent(opUser, chefEnv, "create"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } envResponse["uri"] = util.ObjURL(chefEnv) w.WriteHeader(http.StatusCreated) default: jsonErrorReport(w, r, "Unrecognized method", http.StatusMethodNotAllowed) return } } else if pathArrayLen == 2 { /* All of the 2 element operations return the environment * object, so we do the json encoding in this block and return * out. */ envName := pathArray[1] env, err := environment.Get(envName) delEnv := false /* Set this to delete the environment after * sending the json. */ if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } switch r.Method { case "GET", "DELETE": /* We don't actually have to do much here. */ if r.Method == "DELETE" { if !opUser.IsAdmin() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } if envName == "_default" { jsonErrorReport(w, r, "The '_default' environment cannot be modified.", http.StatusMethodNotAllowed) return } delEnv = true } else { if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } } case "PUT": if !opUser.IsAdmin() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } envData, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return } if envData == nil { jsonErrorReport(w, r, "No environment data in body at all!", http.StatusBadRequest) return } if _, ok := envData["name"]; !ok { //envData["name"] = envName jsonErrorReport(w, r, "Environment name missing", http.StatusBadRequest) return } jsonName, sterr := util.ValidateAsString(envData["name"]) if sterr != nil { jsonErrorReport(w, r, sterr.Error(), sterr.Status()) return } else if jsonName == "" { jsonErrorReport(w, r, "Environment name missing", http.StatusBadRequest) return } if envName != envData["name"].(string) { env, err = environment.Get(envData["name"].(string)) if err == nil { jsonErrorReport(w, r, "Environment already exists", http.StatusConflict) return } var eerr util.Gerror env, eerr = environment.NewFromJSON(envData) if eerr != nil { jsonErrorReport(w, r, eerr.Error(), eerr.Status()) return } w.WriteHeader(http.StatusCreated) oldenv, olderr := environment.Get(envName) if olderr == nil { oldenv.Delete() } } else { if jsonName == "" { envData["name"] = envName } if err := env.UpdateFromJSON(envData); err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } } if err := env.Save(); err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } if lerr := loginfo.LogEvent(opUser, env, "modify"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } default: jsonErrorReport(w, r, "Unrecognized method", http.StatusMethodNotAllowed) return } enc := json.NewEncoder(w) if err := enc.Encode(&env); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } if delEnv { err := env.Delete() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } if lerr := loginfo.LogEvent(opUser, env, "delete"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } } return } else if pathArrayLen == 3 { envName := pathArray[1] op := pathArray[2] if op == "cookbook_versions" && r.Method != "POST" || op != "cookbook_versions" && r.Method != "GET" { jsonErrorReport(w, r, "Unrecognized method", http.StatusMethodNotAllowed) return } if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } env, err := environment.Get(envName) if err != nil { var errMsg string // bleh, stupid errors if err.Status() == http.StatusNotFound && (op != "recipes" && op != "cookbooks") { errMsg = fmt.Sprintf("environment '%s' not found", envName) } else { errMsg = err.Error() } jsonErrorReport(w, r, errMsg, err.Status()) return } switch op { case "cookbook_versions": /* Chef Server API docs aren't even remotely * right here. What it actually wants is the * usual hash of info for the latest or * constrained version. Weird. */ cbVer, jerr := parseObjJSON(r.Body) if jerr != nil { errmsg := jerr.Error() if !strings.Contains(errmsg, "Field") { errmsg = "invalid JSON" } else { errmsg = jerr.Error() } jsonErrorReport(w, r, errmsg, http.StatusBadRequest) return } if _, ok := cbVer["run_list"]; !ok { jsonErrorReport(w, r, "POSTed JSON badly formed.", http.StatusMethodNotAllowed) return } deps, derr := cookbook.DependsCookbooks(cbVer["run_list"].([]string), env.CookbookVersions) if derr != nil { switch derr := derr.(type) { case *cookbook.DependsError: // In 1.0.0-dev, there's a // JSONErrorMapReport function in util. // Use that when moving this forward errMap := make(map[string][]map[string]interface{}) errMap["error"] = make([]map[string]interface{}, 1) errMap["error"][0] = derr.ErrMap() w.WriteHeader(http.StatusPreconditionFailed) enc := json.NewEncoder(w) if jerr := enc.Encode(&errMap); jerr != nil { logger.Errorf(jerr.Error()) } default: jsonErrorReport(w, r, derr.Error(), http.StatusPreconditionFailed) } return } /* Need our own encoding here too. */ enc := json.NewEncoder(w) if err := enc.Encode(&deps); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } return case "cookbooks": envResponse = env.AllCookbookHash(numResults) case "nodes": nodeList, err := node.GetFromEnv(envName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } for _, chefNode := range nodeList { envResponse[chefNode.Name] = util.ObjURL(chefNode) } case "recipes": envRecipes := env.RecipeList() /* And... we have to do our own json response * here. Hmph. */ /* TODO: make the JSON encoding stuff its own * function. Dunno why I never thought of that * before now for this. */ enc := json.NewEncoder(w) if err := enc.Encode(&envRecipes); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } return default: jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } } else if pathArrayLen == 4 { envName := pathArray[1] /* op is either "cookbooks" or "roles", and opName is the name * of the object op refers to. */ op := pathArray[2] opName := pathArray[3] if r.Method != "GET" { jsonErrorReport(w, r, "Method not allowed", http.StatusMethodNotAllowed) return } if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } env, err := environment.Get(envName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } /* Biting the bullet and not redirecting this to * /roles/NAME/environments/NAME. The behavior is exactly the * same, but it makes clients and chef-pedant somewhat unhappy * to not have this way available. */ if op == "roles" { role, err := role.Get(opName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } var runList []string if envName == "_default" { runList = role.RunList } else { runList = role.EnvRunLists[envName] } envResponse["run_list"] = runList } else if op == "cookbooks" { cb, err := cookbook.Get(opName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } /* Here and, I think, here only, if num_versions isn't * set it's supposed to return ALL matching versions. * API docs are wrong here. */ if numResults == "" { numResults = "all" } envResponse[opName] = cb.ConstrainedInfoHash(numResults, env.CookbookVersions[opName]) } else { /* Not an op we know. */ jsonErrorReport(w, r, "Bad request - too many elements in path", http.StatusBadRequest) return } } else { /* Bad number of path elements. */ jsonErrorReport(w, r, "Bad request - too many elements in path", http.StatusBadRequest) return } enc := json.NewEncoder(w) if err := enc.Encode(&envResponse); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } }
func shoveyHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } if !opUser.IsAdmin() && r.Method != "PUT" { jsonErrorReport(w, r, "you cannot perform this action", http.StatusForbidden) return } if !config.Config.UseShovey { jsonErrorReport(w, r, "shovey is not enabled", http.StatusPreconditionFailed) return } pathArray := splitPath(r.URL.Path) pathArrayLen := len(pathArray) if pathArrayLen < 2 || pathArrayLen > 4 || pathArray[1] == "" { jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } op := pathArray[1] shoveyResponse := make(map[string]interface{}) switch op { case "jobs": switch r.Method { case "GET": switch pathArrayLen { case 4: shove, err := shovey.Get(pathArray[2]) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } sj, err := shove.GetRun(pathArray[3]) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } shoveyResponse, err = sj.ToJSON() if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } case 3: shove, err := shovey.Get(pathArray[2]) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } shoveyResponse, err = shove.ToJSON() if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } default: shoveyIDs, err := shovey.AllShoveyIDs() if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } enc := json.NewEncoder(w) if jerr := enc.Encode(&shoveyIDs); err != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusInternalServerError) } return } case "POST": if pathArrayLen != 2 { jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } shvData, err := parseObjJSON(r.Body) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusBadRequest) return } logger.Debugf("shvData: %v", shvData) var quorum string var timeout int var ok bool if quorum, ok = shvData["quorum"].(string); !ok { quorum = "100%" } logger.Debugf("run_timeout is a %T", shvData["run_timeout"]) if t, ok := shvData["run_timeout"].(float64); !ok { timeout = 300 } else { timeout = int(t) } var nodeNames []string if shvNodes, ok := shvData["nodes"].([]interface{}); ok { if len(shvNodes) == 0 { jsonErrorReport(w, r, "no nodes provided", http.StatusBadRequest) return } nodeNames = make([]string, len(shvNodes)) for i, v := range shvNodes { nodeNames[i] = v.(string) } } else { jsonErrorReport(w, r, "node list not an array", http.StatusBadRequest) return } s, gerr := shovey.New(shvData["command"].(string), timeout, quorum, nodeNames) if gerr != nil { jsonErrorReport(w, r, gerr.Error(), gerr.Status()) return } gerr = s.Start() if gerr != nil { jsonErrorReport(w, r, gerr.Error(), gerr.Status()) return } shoveyResponse["id"] = s.RunID shoveyResponse["uri"] = util.CustomURL(fmt.Sprintf("/shovey/jobs/%s", s.RunID)) case "PUT": switch pathArrayLen { case 3: if pathArray[2] != "cancel" { jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } cancelData, perr := parseObjJSON(r.Body) if perr != nil { jsonErrorReport(w, r, perr.Error(), http.StatusBadRequest) return } var nodeNames []string runID, ok := cancelData["run_id"].(string) if !ok { jsonErrorReport(w, r, "No shovey run ID provided, or provided id was invalid", http.StatusBadRequest) return } if nn, ok := cancelData["nodes"].([]interface{}); ok { for _, v := range nn { nodeNames = append(nodeNames, v.(string)) } } shove, err := shovey.Get(runID) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } if len(nodeNames) != 0 { serr := shove.CancelRuns(nodeNames) if serr != nil { logger.Debugf("Error cancelling runs: %s", serr.Error()) jsonErrorReport(w, r, err.Error(), err.Status()) return } } else { err = shove.Cancel() if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } } shoveyResponse, err = shove.ToJSON() if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } case 4: sjData, perr := parseObjJSON(r.Body) if perr != nil { jsonErrorReport(w, r, perr.Error(), http.StatusBadRequest) return } nodeName := pathArray[3] logger.Debugf("sjData: %v", sjData) shove, err := shovey.Get(pathArray[2]) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } sj, err := shove.GetRun(nodeName) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } err = sj.UpdateFromJSON(sjData) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } shoveyResponse["id"] = shove.RunID shoveyResponse["node"] = nodeName shoveyResponse["response"] = "ok" default: jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } default: jsonErrorReport(w, r, "Unrecognized method", http.StatusMethodNotAllowed) return } case "stream": if pathArrayLen != 4 { jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } switch r.Method { case "GET": var seq int r.ParseForm() if s, found := r.Form["sequence"]; found { if len(s) < 0 { jsonErrorReport(w, r, "invalid sequence", http.StatusBadRequest) return } var err error seq, err = strconv.Atoi(s[0]) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusBadRequest) return } } var outType string if o, found := r.Form["output_type"]; found { if len(o) < 0 { jsonErrorReport(w, r, "invalid output type", http.StatusBadRequest) return } outType = o[0] if outType != "stdout" && outType != "stderr" && outType != "both" { jsonErrorReport(w, r, "output type must be 'stdout', 'stderr', or 'both'", http.StatusBadRequest) return } } else { outType = "stdout" } shove, err := shovey.Get(pathArray[2]) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } sj, err := shove.GetRun(pathArray[3]) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } stream, err := sj.GetStreamOutput(outType, seq) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } combinedOutput, err := sj.CombineStreamOutput(outType, seq) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } shoveyResponse["run_id"] = sj.ShoveyUUID shoveyResponse["node_name"] = sj.NodeName shoveyResponse["output_type"] = outType shoveyResponse["is_last"] = false if len(stream) != 0 { shoveyResponse["last_seq"] = stream[len(stream)-1].Seq shoveyResponse["is_last"] = stream[len(stream)-1].IsLast } shoveyResponse["output"] = combinedOutput case "PUT": streamData, serr := parseObjJSON(r.Body) logger.Debugf("streamData: %v", streamData) if serr != nil { jsonErrorReport(w, r, serr.Error(), http.StatusBadRequest) return } shove, err := shovey.Get(pathArray[2]) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } sj, err := shove.GetRun(pathArray[3]) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } output, ok := streamData["output"].(string) if !ok { oerr := util.Errorf("invalid output") jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } outputType, ok := streamData["output_type"].(string) if !ok { oerr := util.Errorf("invalid output type") jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } isLast, ok := streamData["is_last"].(bool) if !ok { oerr := util.Errorf("invalid is_last") jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } seqFloat, ok := streamData["seq"].(float64) if !ok { oerr := util.Errorf("invalid seq") jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } seq := int(seqFloat) err = sj.AddStreamOutput(output, outputType, seq, isLast) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } shoveyResponse["response"] = "ok" default: jsonErrorReport(w, r, "Unrecognized method", http.StatusMethodNotAllowed) return } default: jsonErrorReport(w, r, "Unrecognized operation", http.StatusBadRequest) return } enc := json.NewEncoder(w) if jerr := enc.Encode(&shoveyResponse); jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusInternalServerError) } return }
func nodeHandling(w http.ResponseWriter, r *http.Request) map[string]string { /* We're dealing with nodes, then. */ nodeResponse := make(map[string]string) opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return nil } switch r.Method { case "GET": if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to take this action.", http.StatusForbidden) return nil } nodeList := node.GetList() for _, k := range nodeList { itemURL := fmt.Sprintf("/nodes/%s", k) nodeResponse[k] = util.CustomURL(itemURL) } case "POST": if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to take this action.", http.StatusForbidden) return nil } nodeData, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return nil } nodeName, sterr := util.ValidateAsString(nodeData["name"]) if sterr != nil { jsonErrorReport(w, r, sterr.Error(), http.StatusBadRequest) return nil } chefNode, _ := node.Get(nodeName) if chefNode != nil { httperr := fmt.Errorf("Node already exists") jsonErrorReport(w, r, httperr.Error(), http.StatusConflict) return nil } var nerr util.Gerror chefNode, nerr = node.NewFromJSON(nodeData) if nerr != nil { jsonErrorReport(w, r, nerr.Error(), nerr.Status()) return nil } err := chefNode.Save() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return nil } err = chefNode.UpdateStatus("new") if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return nil } if lerr := loginfo.LogEvent(opUser, chefNode, "create"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return nil } nodeResponse["uri"] = util.ObjURL(chefNode) w.WriteHeader(http.StatusCreated) default: jsonErrorReport(w, r, "Method not allowed for nodes", http.StatusMethodNotAllowed) return nil } return nodeResponse }
// user handling func userHandling(w http.ResponseWriter, r *http.Request) map[string]string { userResponse := make(map[string]string) opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return nil } switch r.Method { case "GET": userList := user.GetList() for _, k := range userList { /* Make sure it's a client and not a user. */ itemURL := fmt.Sprintf("/users/%s", k) userResponse[k] = util.CustomURL(itemURL) } case "POST": userData, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return nil } if averr := util.CheckAdminPlusValidator(userData); averr != nil { jsonErrorReport(w, r, averr.Error(), averr.Status()) return nil } if !opUser.IsAdmin() && !opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to take this action.", http.StatusForbidden) return nil } else if !opUser.IsAdmin() && opUser.IsValidator() { if aerr := opUser.CheckPermEdit(userData, "admin"); aerr != nil { jsonErrorReport(w, r, aerr.Error(), aerr.Status()) return nil } if verr := opUser.CheckPermEdit(userData, "validator"); verr != nil { jsonErrorReport(w, r, verr.Error(), verr.Status()) return nil } } userName, sterr := util.ValidateAsString(userData["name"]) if sterr != nil || userName == "" { err := fmt.Errorf("Field 'name' missing") jsonErrorReport(w, r, err.Error(), http.StatusBadRequest) return nil } chefUser, err := user.NewFromJSON(userData) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return nil } if publicKey, pkok := userData["public_key"]; !pkok { var perr error if userResponse["private_key"], perr = chefUser.GenerateKeys(); perr != nil { jsonErrorReport(w, r, perr.Error(), http.StatusInternalServerError) return nil } } else { switch publicKey := publicKey.(type) { case string: if pkok, pkerr := user.ValidatePublicKey(publicKey); !pkok { jsonErrorReport(w, r, pkerr.Error(), pkerr.Status()) return nil } chefUser.SetPublicKey(publicKey) case nil: var perr error if userResponse["private_key"], perr = chefUser.GenerateKeys(); perr != nil { jsonErrorReport(w, r, perr.Error(), http.StatusInternalServerError) return nil } default: jsonErrorReport(w, r, "Bad public key", http.StatusBadRequest) return nil } } /* If we make it here, we want the public key in the * response. I think. */ userResponse["public_key"] = chefUser.PublicKey() chefUser.Save() if lerr := loginfo.LogEvent(opUser, chefUser, "create"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return nil } userResponse["uri"] = util.ObjURL(chefUser) w.WriteHeader(http.StatusCreated) default: jsonErrorReport(w, r, "Method not allowed for clients or users", http.StatusMethodNotAllowed) return nil } return userResponse }
func roleHandling(w http.ResponseWriter, r *http.Request) map[string]string { roleResponse := make(map[string]string) opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return nil } switch r.Method { case "GET": if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to take this action.", http.StatusForbidden) return nil } roleList := role.GetList() for _, k := range roleList { itemURL := fmt.Sprintf("/roles/%s", k) roleResponse[k] = util.CustomURL(itemURL) } case "POST": if !opUser.IsAdmin() { jsonErrorReport(w, r, "You are not allowed to take this action.", http.StatusForbidden) return nil } roleData, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return nil } if _, ok := roleData["name"].(string); !ok { jsonErrorReport(w, r, "Role name missing", http.StatusBadRequest) return nil } chefRole, _ := role.Get(roleData["name"].(string)) if chefRole != nil { httperr := fmt.Errorf("Role already exists") jsonErrorReport(w, r, httperr.Error(), http.StatusConflict) return nil } var nerr util.Gerror chefRole, nerr = role.NewFromJSON(roleData) if nerr != nil { jsonErrorReport(w, r, nerr.Error(), nerr.Status()) return nil } err := chefRole.Save() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return nil } if lerr := loginfo.LogEvent(opUser, chefRole, "create"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return nil } roleResponse["uri"] = util.ObjURL(chefRole) w.WriteHeader(http.StatusCreated) default: jsonErrorReport(w, r, "Method not allowed for roles", http.StatusMethodNotAllowed) return nil } return roleResponse }
// The whole list func eventListHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } // Look for offset and limit parameters r.ParseForm() var offset, limit, purgeFrom int if o, found := r.Form["offset"]; found { if len(o) < 0 { jsonErrorReport(w, r, "invalid offsets", http.StatusBadRequest) return } var err error offset, err = strconv.Atoi(o[0]) if err != nil { jsonErrorReport(w, r, "invalid offset converstion to int", http.StatusBadRequest) return } if offset < 0 { jsonErrorReport(w, r, "invalid negative offset value", http.StatusBadRequest) return } } else { offset = 0 } var limitFound bool if l, found := r.Form["limit"]; found { limitFound = true if len(l) < 0 { jsonErrorReport(w, r, "invalid limit", http.StatusBadRequest) return } var err error limit, err = strconv.Atoi(l[0]) if err != nil { jsonErrorReport(w, r, "invalid limit converstion to int", http.StatusBadRequest) return } if limit < 0 { jsonErrorReport(w, r, "invalid negative limit value", http.StatusBadRequest) return } } if p, found := r.Form["purge"]; found { if len(p) < 0 { jsonErrorReport(w, r, "invalid purge id", http.StatusBadRequest) return } var err error purgeFrom, err = strconv.Atoi(p[0]) if err != nil { jsonErrorReport(w, r, "invalid purge from converstion to int", http.StatusBadRequest) return } if purgeFrom < 0 { jsonErrorReport(w, r, "invalid negative purgeFrom value", http.StatusBadRequest) return } } paramStrs := []string{"from", "until", "action", "object_type", "object_name", "doer"} searchParams := make(map[string]string, 6) for _, v := range paramStrs { if st, found := r.Form[v]; found { if len(st) < 0 { jsonErrorReport(w, r, "invalid "+v, http.StatusBadRequest) return } searchParams[v] = st[0] } } switch r.Method { case "GET": if !opUser.IsAdmin() { jsonErrorReport(w, r, "You must be an admin to do that", http.StatusForbidden) return } var leList []*loginfo.LogInfo var err error if limitFound { leList, err = loginfo.GetLogInfos(searchParams, offset, limit) } else { leList, err = loginfo.GetLogInfos(searchParams, offset) } if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusBadRequest) return } leResp := make([]map[string]interface{}, len(leList)) for i, v := range leList { leResp[i] = make(map[string]interface{}) leResp[i]["event"] = v leURL := fmt.Sprintf("/events/%d", v.ID) leResp[i]["url"] = util.CustomURL(leURL) } enc := json.NewEncoder(w) if err := enc.Encode(&leResp); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } case "DELETE": if !opUser.IsAdmin() { jsonErrorReport(w, r, "You must be an admin to do that", http.StatusForbidden) return } purged, err := loginfo.PurgeLogInfos(purgeFrom) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusBadRequest) } leResp := make(map[string]string) leResp["purged"] = fmt.Sprintf("Purged %d logged events", purged) enc := json.NewEncoder(w) if err := enc.Encode(&leResp); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } default: jsonErrorReport(w, r, "Method not allowed", http.StatusMethodNotAllowed) return } }
func cookbookListerSQL(numResults interface{}) map[string]interface{} { var numVersions int allVersions := false cl := make(map[string]interface{}) if numResults != "" && numResults != "all" { numVersions, _ = strconv.Atoi(numResults.(string)) } else if numResults == "" { numVersions = 1 } else { allVersions = true } var sqlStatement string if config.Config.UseMySQL { sqlStatement = "SELECT version, name FROM joined_cookbook_version ORDER BY name, major_ver desc, minor_ver desc, patch_ver desc" } else if config.Config.UsePostgreSQL { sqlStatement = "SELECT version, name FROM goiardi.joined_cookbook_version ORDER BY name, major_ver desc, minor_ver desc, patch_ver desc" } stmt, err := datastore.Dbh.Prepare(sqlStatement) if err != nil { log.Fatal(err) } defer stmt.Close() rows, qerr := stmt.Query() if qerr != nil { if qerr == sql.ErrNoRows { return cl } log.Fatal(qerr) } scratch := make(map[string][]string) for rows.Next() { var n, v string err := rows.Scan(&v, &n) if err != nil { log.Fatal(err) } scratch[n] = append(scratch[n], v) } rows.Close() if err = rows.Err(); err != nil { log.Fatal(err) } for name, versions := range scratch { nr := 0 cburl := fmt.Sprintf("/cookbooks/%s", name) cb := make(map[string]interface{}) cb["url"] = util.CustomURL(cburl) cb["versions"] = make([]interface{}, 0) for _, ver := range versions { if !allVersions && nr >= numVersions { break } cv := make(map[string]string) cv["url"] = util.CustomURL(fmt.Sprintf("/cookbooks/%s/%s", name, ver)) cv["version"] = ver cb["versions"] = append(cb["versions"].([]interface{}), cv) nr++ } cl[name] = cb } return cl }
func universeSQL() map[string]map[string]interface{} { universe := make(map[string]map[string]interface{}) var ( major int64 minor int64 patch int64 ) var name string var sqlStatement string if config.Config.UseMySQL { sqlStatement = "SELECT major_ver, minor_ver, patch_ver, c.name, metadata FROM cookbook_versions cv LEFT JOIN cookbooks c ON cv.cookbook_id = c.id ORDER BY cv.cookbook_id, major_ver DESC, minor_ver DESC, patch_ver DESC" } else if config.Config.UsePostgreSQL { sqlStatement = "SELECT major_ver, minor_ver, patch_ver, c.name, metadata->>'dependencies' FROM goiardi.cookbook_versions cv LEFT JOIN goiardi.cookbooks c ON cv.cookbook_id = c.id ORDER BY cv.cookbook_id, major_ver DESC, minor_ver DESC, patch_ver DESC" } stmt, err := datastore.Dbh.Prepare(sqlStatement) if err != nil { log.Fatal(err) } defer stmt.Close() rows, qerr := stmt.Query() if qerr != nil { if qerr == sql.ErrNoRows { return universe } log.Fatal(qerr) } for rows.Next() { var metb sql.RawBytes metadata := make(map[string]interface{}) u := make(map[string]interface{}) err := rows.Scan(&major, &minor, &patch, &name, &metb) if err != nil { log.Fatal(err) } err = datastore.DecodeBlob(metb, &metadata) if err != nil { log.Fatal(err) } version := fmt.Sprintf("%d.%d.%d", major, minor, patch) customURL := fmt.Sprintf("/cookbook/%s/%s", name, version) u["location_path"] = util.CustomURL(customURL) u["location_type"] = "chef_server" if config.Config.UsePostgreSQL { u["dependencies"] = metadata } else { u["dependencies"] = metadata["dependencies"] } if _, ok := universe[name]; !ok { universe[name] = make(map[string]interface{}) } universe[name][version] = u } rows.Close() if err = rows.Err(); err != nil { log.Fatal(err) } return universe }
func statusHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } if !opUser.IsAdmin() { jsonErrorReport(w, r, "You must be an admin to do that", http.StatusForbidden) return } pathArray := splitPath(r.URL.Path) if len(pathArray) < 3 { jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } var statusResponse interface{} switch r.Method { case "GET": /* pathArray[1] will tell us what operation we're doing */ switch pathArray[1] { // /status/all/nodes case "all": if len(pathArray) != 3 { jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } if pathArray[2] != "nodes" { jsonErrorReport(w, r, "Invalid object to get status for", http.StatusBadRequest) return } nodes := node.AllNodes() sr := make([]map[string]string, len(nodes)) for i, n := range nodes { ns, err := n.LatestStatus() if err != nil { nsbad := make(map[string]string) nsbad["node_name"] = n.Name nsbad["status"] = "no record" sr[i] = nsbad continue } sr[i] = ns.ToJSON() nsurl := fmt.Sprintf("/status/node/%s/latest", n.Name) sr[i]["url"] = util.CustomURL(nsurl) } statusResponse = sr // /status/node/<nodeName>/(all|latest) case "node": if len(pathArray) != 4 { jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } nodeName := pathArray[2] op := pathArray[3] n, gerr := node.Get(nodeName) if gerr != nil { jsonErrorReport(w, r, gerr.Error(), gerr.Status()) return } switch op { case "latest": ns, err := n.LatestStatus() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } statusResponse = ns.ToJSON() case "all": ns, err := n.AllStatuses() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } sr := make([]map[string]string, len(ns)) for i, v := range ns { sr[i] = v.ToJSON() } statusResponse = sr default: jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } default: jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } default: jsonErrorReport(w, r, "Method not allowed", http.StatusMethodNotAllowed) return } enc := json.NewEncoder(w) if err := enc.Encode(&statusResponse); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } }
func searchHandler(w http.ResponseWriter, r *http.Request) { /* ... and we need search to run the environment tests, so here we * go. */ w.Header().Set("Content-Type", "application/json") searchResponse := make(map[string]interface{}) pathArray := splitPath(r.URL.Path) pathArrayLen := len(pathArray) opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } /* set up query params for searching */ var ( paramQuery string paramsRows int sortOrder string start int ) r.ParseForm() if q, found := r.Form["q"]; found { if len(q) < 0 { jsonErrorReport(w, r, "No query string specified for search", http.StatusBadRequest) return } paramQuery = q[0] } else if pathArrayLen != 1 { /* default to "*:*" for a search term */ paramQuery = "*:*" } if pr, found := r.Form["rows"]; found { if len(pr) > 0 { paramsRows, _ = strconv.Atoi(pr[0]) } } else { paramsRows = 1000 } sortOrder = "id ASC" if s, found := r.Form["sort"]; found { if len(s) > 0 { if s[0] != "" { sortOrder = s[0] } } else { sortOrder = "id ASC" } } if st, found := r.Form["start"]; found { if len(st) > 0 { start, _ = strconv.Atoi(st[0]) } } else { start = 0 } var searcher search.Searcher if config.Config.PgSearch { searcher = &search.PostgresSearch{} } else { searcher = &search.TrieSearch{} } if pathArrayLen == 1 { /* base end points */ switch r.Method { case "GET": if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } searchEndpoints := searcher.GetEndpoints() for _, s := range searchEndpoints { searchResponse[s] = util.CustomURL(fmt.Sprintf("/search/%s", s)) } default: jsonErrorReport(w, r, "Method not allowed", http.StatusMethodNotAllowed) return } } else if pathArrayLen == 2 { switch r.Method { case "GET", "POST": if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } var qerr error paramQuery, qerr = url.QueryUnescape(paramQuery) if qerr != nil { jsonErrorReport(w, r, qerr.Error(), http.StatusBadRequest) return } /* start figuring out what comes in POSTS now, * so the partial search tests don't complain * anymore. */ var partialData map[string]interface{} if r.Method == "POST" { var perr error partialData, perr = parseObjJSON(r.Body) if perr != nil { jsonErrorReport(w, r, perr.Error(), http.StatusBadRequest) return } } idx := pathArray[1] res, err := searcher.Search(idx, paramQuery, paramsRows, sortOrder, start, partialData) if err != nil { statusCode := http.StatusBadRequest re := regexp.MustCompile(`^I don't know how to search for .*? data objects.`) if re.MatchString(err.Error()) { statusCode = http.StatusNotFound } jsonErrorReport(w, r, err.Error(), statusCode) return } searchResponse["total"] = len(res) searchResponse["start"] = start searchResponse["rows"] = res default: jsonErrorReport(w, r, "Method not allowed", http.StatusMethodNotAllowed) return } } else { /* Say what? Bad request. */ jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } enc := json.NewEncoder(w) if err := enc.Encode(&searchResponse); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } }