func principalHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") principalName := r.URL.Path[12:] switch r.Method { case "GET": chefActor, err := actor.GetReqUser(principalName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } var chefType string if chefActor.IsUser() { chefType = "user" } else { chefType = "client" } jsonPrincipal := map[string]interface{}{ "name": chefActor.GetName(), "type": chefType, "public_key": chefActor.PublicKey(), } enc := json.NewEncoder(w) if encerr := enc.Encode(&jsonPrincipal); encerr != nil { jsonErrorReport(w, r, encerr.Error(), http.StatusInternalServerError) return } default: jsonErrorReport(w, r, "Unrecognized method for principals!", http.StatusMethodNotAllowed) } }
func reindexHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") reindexResponse := 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 } switch r.Method { case "POST": if !opUser.IsAdmin() { jsonErrorReport(w, r, "You are not allowed to perform that action.", http.StatusForbidden) return } reindexAll() reindexResponse["reindex"] = "OK" default: jsonErrorReport(w, r, "Method not allowed. If you're trying to do something with a data bag named 'reindex', it's not going to work I'm afraid.", http.StatusMethodNotAllowed) return } enc := json.NewEncoder(w) if err := enc.Encode(&reindexResponse); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } }
// CheckHeader checks the signed headers sent by the client against the expected // result assembled from the request headers to verify their authorization. func CheckHeader(userID string, r *http.Request) util.Gerror { user, err := actor.GetReqUser(userID) if err != nil { gerr := util.Errorf("Failed to authenticate as '%s'. Ensure that your node_name and client key are correct.", userID) gerr.SetStatus(http.StatusUnauthorized) return gerr } return AuthenticateHeader(user.PublicKey(), config.Config.TimeSlewDur, r) }
// Individual log events func eventHandler(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 } eventID, aerr := strconv.Atoi(r.URL.Path[8:]) if aerr != nil { jsonErrorReport(w, r, aerr.Error(), http.StatusBadRequest) return } switch r.Method { case "GET": if !opUser.IsAdmin() { jsonErrorReport(w, r, "You must be an admin to do that", http.StatusForbidden) return } le, err := loginfo.Get(eventID) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } enc := json.NewEncoder(w) if err = enc.Encode(&le); 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 } le, err := loginfo.Get(eventID) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } err = le.Delete() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } enc := json.NewEncoder(w) if err = enc.Encode(&le); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } default: jsonErrorReport(w, r, "Method not allowed", http.StatusMethodNotAllowed) return } return }
func reportHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") protocolVersion := r.Header.Get("X-Ops-Reporting-Protocol-Version") if protocolVersion == "" { // try a param (makes working with webui easier) form, e := url.ParseQuery(r.URL.RawQuery) if e != nil { jsonErrorReport(w, r, e.Error(), http.StatusBadRequest) return } if p, f := form["protocol-version"]; f { if len(p) > 0 { protocolVersion = p[0] } } } // someday there may be other protocol versions if protocolVersion != "0.1.0" { jsonErrorReport(w, r, "Unsupported reporting protocol version", http.StatusNotFound) return } opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } // TODO: some params for time ranges exist and need to be handled // properly pathArray := splitPath(r.URL.Path) pathArrayLen := len(pathArray) reportResponse := make(map[string]interface{}) switch r.Method { case "GET": // Making an informed guess that admin rights are needed // to see the node run reports r.ParseForm() var rows int var from, until time.Time var status string if fr, found := r.Form["rows"]; found { if len(fr) < 0 { jsonErrorReport(w, r, "invalid rows", http.StatusBadRequest) return } var err error rows, err = strconv.Atoi(fr[0]) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusBadRequest) return } } else { // default is 10 rows = 10 } if ff, found := r.Form["from"]; found { if len(ff) < 0 { jsonErrorReport(w, r, "invalid from", http.StatusBadRequest) return } fromUnix, err := strconv.ParseInt(ff[0], 10, 64) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusBadRequest) return } from = time.Unix(fromUnix, 0) } else { from = time.Now().Add(-(time.Duration(24*90) * time.Hour)) } if fu, found := r.Form["until"]; found { if len(fu) < 0 { jsonErrorReport(w, r, "invalid until", http.StatusBadRequest) return } untilUnix, err := strconv.ParseInt(fu[0], 10, 64) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusBadRequest) return } until = time.Unix(untilUnix, 0) } else { until = time.Now() } if st, found := r.Form["status"]; found { if len(st) < 0 { jsonErrorReport(w, r, "invalid status", http.StatusBadRequest) return } status = st[0] if status != "started" && status != "success" && status != "failure" { jsonErrorReport(w, r, "invalid status given", http.StatusBadRequest) return } } // If the end time is more than 90 days ahead of the // start time, give an error if from.Truncate(time.Hour).Sub(until.Truncate(time.Hour)) >= (time.Duration(24*90) * time.Hour) { msg := fmt.Sprintf("End time %s is too far ahead of start time %s (max 90 days)", until.String(), from.String()) jsonErrorReport(w, r, msg, http.StatusNotAcceptable) return } if !opUser.IsAdmin() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } if pathArrayLen < 3 || pathArrayLen > 4 { jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } op := pathArray[1] if op == "nodes" && pathArrayLen == 4 { nodeName := pathArray[2] runs, nerr := report.GetNodeList(nodeName, from, until, rows, status) if nerr != nil { jsonErrorReport(w, r, nerr.Error(), http.StatusInternalServerError) return } reportResponse["run_history"] = runs } else if op == "org" { if pathArrayLen == 4 { runID := pathArray[3] run, err := report.Get(runID) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } reportResponse = formatRunShow(run) } else { runs, rerr := report.GetReportList(from, until, rows, status) if rerr != nil { jsonErrorReport(w, r, rerr.Error(), http.StatusInternalServerError) return } reportResponse["run_history"] = runs } } else { jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } case "POST": // Can't use the usual parseObjJSON function here, since // the reporting "run_list" type is a string rather // than []interface{}. jsonReport := make(map[string]interface{}) dec := json.NewDecoder(r.Body) if jerr := dec.Decode(&jsonReport); jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return } if pathArrayLen < 4 || pathArrayLen > 5 { jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } nodeName := pathArray[2] if pathArrayLen == 4 { rep, err := report.NewFromJSON(nodeName, jsonReport) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } // what's the expected response? serr := rep.Save() if serr != nil { jsonErrorReport(w, r, serr.Error(), http.StatusInternalServerError) return } reportResponse["run_detail"] = rep } else { runID := pathArray[4] rep, err := report.Get(runID) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } err = rep.UpdateFromJSON(jsonReport) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } serr := rep.Save() if serr != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } // .... and? reportResponse["run_detail"] = rep } default: jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } enc := json.NewEncoder(w) if err := enc.Encode(&reportResponse); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } }
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 }
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 }
func nodeHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") nodeName := r.URL.Path[7:] opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } /* So, what are we doing? Depends on the HTTP method, of course */ switch r.Method { case "GET", "DELETE": if opUser.IsValidator() || !opUser.IsAdmin() && r.Method == "DELETE" && !(opUser.IsClient() && opUser.(*client.Client).NodeName == nodeName) { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } chefNode, nerr := node.Get(nodeName) if nerr != nil { jsonErrorReport(w, r, nerr.Error(), http.StatusNotFound) return } enc := json.NewEncoder(w) if err := enc.Encode(&chefNode); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } if r.Method == "DELETE" { err := chefNode.Delete() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } if lerr := loginfo.LogEvent(opUser, chefNode, "delete"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } } case "PUT": if !opUser.IsAdmin() && !(opUser.IsClient() && opUser.(*client.Client).NodeName == nodeName) { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } nodeData, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return } chefNode, kerr := node.Get(nodeName) if kerr != nil { jsonErrorReport(w, r, kerr.Error(), http.StatusNotFound) return } /* If nodeName and nodeData["name"] don't match, we * need to make a new node. Make sure that node doesn't * exist. */ if _, found := nodeData["name"]; !found { nodeData["name"] = nodeName } jsonName, sterr := util.ValidateAsString(nodeData["name"]) if sterr != nil { jsonErrorReport(w, r, sterr.Error(), http.StatusBadRequest) return } if nodeName != jsonName && jsonName != "" { jsonErrorReport(w, r, "Node name mismatch.", http.StatusBadRequest) return } if jsonName == "" { nodeData["name"] = nodeName } nerr := chefNode.UpdateFromJSON(nodeData) if nerr != nil { jsonErrorReport(w, r, nerr.Error(), nerr.Status()) return } err := chefNode.Save() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } if lerr := loginfo.LogEvent(opUser, chefNode, "modify"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } enc := json.NewEncoder(w) if err = enc.Encode(&chefNode); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } default: jsonErrorReport(w, r, "Unrecognized method!", http.StatusMethodNotAllowed) } }
func cookbookHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") pathArray := splitPath(r.URL.Path) cookbookResponse := 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 } 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, err.Error(), err.Status()) return } } force := "" if f, fok := r.Form["force"]; fok { if len(f) > 0 { force = f[0] } } pathArrayLen := len(pathArray) /* 1 and 2 length path arrays only support GET */ if pathArrayLen < 3 && r.Method != "GET" { jsonErrorReport(w, r, "Bad request.", http.StatusMethodNotAllowed) return } else if pathArrayLen < 3 && opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } /* chef-pedant is happier when checking if a validator can do something * surprisingly late in the game. It wants the perm checks to be * checked after the method for the end point is checked out as * something it's going to handle, so, for instance, issuing a DELETE * against an endpoint where that isn't allowed should respond with a * 405, rather than a 403, which also makes sense in areas where * validators shouldn't be able to do anything. *shrugs* */ if pathArrayLen == 1 || (pathArrayLen == 2 && pathArray[1] == "") { /* list all cookbooks */ cookbookResponse = cookbook.CookbookLister(numResults) } else if pathArrayLen == 2 { /* info about a cookbook and all its versions */ cookbookName := pathArray[1] /* Undocumented behavior - a cookbook name of _latest gets a * list of the latest versions of all the cookbooks, and _recipe * gets the recipes of the latest cookbooks. */ if cookbookName == "_latest" { cookbookResponse = cookbook.CookbookLatest() } else if cookbookName == "_recipes" { rlist, nerr := cookbook.CookbookRecipes() if nerr != nil { jsonErrorReport(w, r, nerr.Error(), nerr.Status()) return } enc := json.NewEncoder(w) if err := enc.Encode(&rlist); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } return } else { cb, err := cookbook.Get(cookbookName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } /* Strange thing here. The API docs say if num_versions * is not specified to return one cookbook, yet the * spec indicates that if it's not set that all * cookbooks should be returned. Most places *except * here* that's the case, so it can't be changed in * infoHashBase. Explicitly set numResults to all * here. */ if numResults == "" { numResults = "all" } cookbookResponse[cookbookName] = cb.InfoHash(numResults) } } else if pathArrayLen == 3 { /* get information about or manipulate a specific cookbook * version */ cookbookName := pathArray[1] var cookbookVersion string var vererr util.Gerror opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } if r.Method == "GET" && pathArray[2] == "_latest" { // might be other special vers cookbookVersion = pathArray[2] } else { cookbookVersion, vererr = util.ValidateAsVersion(pathArray[2]) if vererr != nil { vererr := util.Errorf("Invalid cookbook version '%s'.", pathArray[2]) jsonErrorReport(w, r, vererr.Error(), vererr.Status()) return } } switch r.Method { case "DELETE", "GET": if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } cb, err := cookbook.Get(cookbookName) if err != nil { if err.Status() == http.StatusNotFound { msg := fmt.Sprintf("Cannot find a cookbook named %s with version %s", cookbookName, cookbookVersion) jsonErrorReport(w, r, msg, err.Status()) } else { jsonErrorReport(w, r, err.Error(), err.Status()) } return } cbv, err := cb.GetVersion(cookbookVersion) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } if r.Method == "DELETE" { if !opUser.IsAdmin() { jsonErrorReport(w, r, "You are not allowed to take this action.", http.StatusForbidden) return } err := cb.DeleteVersion(cookbookVersion) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } if lerr := loginfo.LogEvent(opUser, cbv, "delete"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } /* If all versions are gone, remove the * cookbook - seems to be the desired * behavior. */ if cb.NumVersions() == 0 { if cerr := cb.Delete(); cerr != nil { jsonErrorReport(w, r, cerr.Error(), http.StatusInternalServerError) return } } } else { /* Special JSON rendition of the * cookbook with some but not all of * the fields. */ cookbookResponse = cbv.ToJSON(r.Method) /* Sometimes, but not always, chef needs * empty slices of maps for these * values. Arrrgh. */ /* Doing it this way is absolutely * insane. However, webui really wants * this information, while chef-pedant * absolutely does NOT want it there. * knife seems happy without it as well. * Until such time that this gets * resolved in a non-crazy manner, for * this only send that info back if it's * a webui request. */ if rs := r.Header.Get("X-Ops-Request-Source"); rs == "web" { chkDiv := []string{"definitions", "libraries", "attributes", "providers", "resources", "templates", "root_files", "files"} for _, cd := range chkDiv { if cookbookResponse[cd] == nil { cookbookResponse[cd] = make([]map[string]interface{}, 0) } } } } case "PUT": if !opUser.IsAdmin() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } cbvData, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return } /* First, see if the cookbook already exists, & * if not create it. Second, see if this * specific version of the cookbook exists. If * so, update it, otherwise, create it and set * the latest version as needed. */ cb, err := cookbook.Get(cookbookName) if err != nil { cb, err = cookbook.New(cookbookName) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } /* save it so we get the id with mysql * for creating versions & such */ serr := cb.Save() if serr != nil { jsonErrorReport(w, r, serr.Error(), http.StatusInternalServerError) return } if lerr := loginfo.LogEvent(opUser, cb, "create"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } } cbv, err := cb.GetVersion(cookbookVersion) /* Does the cookbook_name in the URL and what's * in the body match? */ switch t := cbvData["cookbook_name"].(type) { case string: /* Only send this particular * error if the cookbook version * hasn't been created yet. * Instead we want a slightly * different version later. */ if t != cookbookName && cbv == nil { terr := util.Errorf("Field 'name' invalid") jsonErrorReport(w, r, terr.Error(), terr.Status()) return } default: // rather unlikely, I think, to // be able to get here past the // cookbook get. Punk out and // don't do anything } if err != nil { var nerr util.Gerror cbv, nerr = cb.NewVersion(cookbookVersion, cbvData) if nerr != nil { // If the new version failed to // take, and there aren't any // other versions of the cookbook // it needs to be deleted. if cb.NumVersions() == 0 { cb.Delete() } jsonErrorReport(w, r, nerr.Error(), nerr.Status()) return } if lerr := loginfo.LogEvent(opUser, cbv, "create"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) } else { err := cbv.UpdateVersion(cbvData, force) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } gerr := cb.Save() if gerr != nil { jsonErrorReport(w, r, gerr.Error(), http.StatusInternalServerError) return } if lerr := loginfo.LogEvent(opUser, cbv, "modify"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } } /* API docs are wrong. The docs claim that this * should have no response body, but in fact it * wants some (not all) of the cookbook version * data. */ cookbookResponse = cbv.ToJSON(r.Method) default: jsonErrorReport(w, r, "Unrecognized method", http.StatusMethodNotAllowed) return } } else { /* Say what? Bad request. */ jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } enc := json.NewEncoder(w) if err := enc.Encode(&cookbookResponse); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } }
// 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 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) } }
func (h *interceptHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { /* knife sometimes sends URL paths that start with //. Redirecting * worked for GETs, but since it was breaking POSTs and screwing with * GETs with query params, we just clean up the path and move on. */ /* log the URL */ // TODO: set this to verbosity level 4 or so logger.Debugf("Serving %s -- %s", r.URL.Path, r.Method) if r.Method != "CONNECT" { if p := cleanPath(r.URL.Path); p != r.URL.Path { r.URL.Path = p } } /* Make configurable, I guess, but Chef wants it to be 1000000 */ if !strings.HasPrefix(r.URL.Path, "/file_store") && r.ContentLength > config.Config.JSONReqMaxSize { logger.Debugf("Content length was too long for %s", r.URL.Path) http.Error(w, "Content-length too long!", http.StatusRequestEntityTooLarge) // hmm, with 1.5 it gets a broken pipe now if we don't do // anything with the body they're trying to send. Try copying it // to /dev/null. This seems crazy, but merely closing the body // doesn't actually work. io.Copy(ioutil.Discard, r.Body) r.Body.Close() return } else if r.ContentLength > config.Config.ObjMaxSize { http.Error(w, "Content-length waaaaaay too long!", http.StatusRequestEntityTooLarge) return } w.Header().Set("X-Goiardi", "yes") w.Header().Set("X-Goiardi-Version", config.Version) w.Header().Set("X-Chef-Version", config.ChefVersion) apiInfo := fmt.Sprintf("flavor=osc;version:%s;goiardi=%s", config.ChefVersion, config.Version) w.Header().Set("X-Ops-API-Info", apiInfo) userID := r.Header.Get("X-OPS-USERID") if rs := r.Header.Get("X-Ops-Request-Source"); rs == "web" { /* If use-auth is on and disable-webui is on, and this is a * webui connection, it needs to fail. */ if config.Config.DisableWebUI { w.Header().Set("Content-Type", "application/json") logger.Warningf("Attempting to log in through webui, but webui is disabled") jsonErrorReport(w, r, "invalid action", http.StatusUnauthorized) return } /* Check that the user in question with the web request exists. * If not, fail. */ if _, uherr := actor.GetReqUser(userID); uherr != nil { w.Header().Set("Content-Type", "application/json") logger.Warningf("Attempting to use invalid user %s through X-Ops-Request-Source = web", userID) jsonErrorReport(w, r, "invalid action", http.StatusUnauthorized) return } userID = "chef-webui" } /* Only perform the authorization check if that's configured. Bomb with * an error if the check of the headers, timestamps, etc. fails. */ /* No clue why /principals doesn't require authorization. Hrmph. */ if config.Config.UseAuth && !strings.HasPrefix(r.URL.Path, "/file_store") && !(strings.HasPrefix(r.URL.Path, "/principals") && r.Method == "GET") { herr := authentication.CheckHeader(userID, r) if herr != nil { w.Header().Set("Content-Type", "application/json") logger.Errorf("Authorization failure: %s\n", herr.Error()) w.Header().Set("Www-Authenticate", `X-Ops-Sign version="1.0" version="1.1" version="1.2"`) //http.Error(w, herr.Error(), herr.Status()) jsonErrorReport(w, r, herr.Error(), herr.Status()) return } } // Experimental: decompress gzipped requests if r.Header.Get("Content-Encoding") == "gzip" { reader, err := gzip.NewReader(r.Body) if err != nil { w.Header().Set("Content-Type", "application/json") logger.Errorf("Failure decompressing gzipped request body: %s\n", err.Error()) jsonErrorReport(w, r, err.Error(), http.StatusBadRequest) return } r.Body = reader } http.DefaultServeMux.ServeHTTP(w, r) }
func roleHandler(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 } /* Roles are bit weird in that there's /roles/NAME, but also * /roles/NAME/environments and /roles/NAME/environments/NAME, so we'll * split up the whole path to get those values. */ pathArray := splitPath(r.URL.Path) roleName := pathArray[1] chefRole, err := role.Get(roleName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } if len(pathArray) == 2 { /* Normal /roles/NAME case */ switch r.Method { case "GET", "DELETE": if opUser.IsValidator() || (!opUser.IsAdmin() && r.Method == "DELETE") { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } enc := json.NewEncoder(w) if err = enc.Encode(&chefRole); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } if r.Method == "DELETE" { err = chefRole.Delete() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } if lerr := loginfo.LogEvent(opUser, chefRole, "delete"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } } case "PUT": if !opUser.IsAdmin() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } roleData, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return } if _, ok := roleData["name"]; !ok { roleData["name"] = roleName } jsonName, sterr := util.ValidateAsString(roleData["name"]) if sterr != nil { jsonErrorReport(w, r, sterr.Error(), sterr.Status()) return } if roleName != roleData["name"].(string) { jsonErrorReport(w, r, "Role name mismatch", http.StatusBadRequest) return } if jsonName == "" { roleData["name"] = roleName } nerr := chefRole.UpdateFromJSON(roleData) if nerr != nil { jsonErrorReport(w, r, nerr.Error(), nerr.Status()) return } err = chefRole.Save() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } if lerr := loginfo.LogEvent(opUser, chefRole, "modify"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } enc := json.NewEncoder(w) if err = enc.Encode(&chefRole); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } default: jsonErrorReport(w, r, "Unrecognized method!", http.StatusMethodNotAllowed) } } else { var environmentName string if len(pathArray) == 4 { environmentName = pathArray[3] if _, err := environment.Get(environmentName); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } } /* only method for the /roles/NAME/environment stuff is GET */ switch r.Method { case "GET": /* If we have an environment name, return the * environment specific run_list. Otherwise, * return the environments we have run lists * for. Always at least return "_default", * which refers to run_list. */ if opUser.IsValidator() { jsonErrorReport(w, r, "You are not allowed to perform this action", http.StatusForbidden) return } enc := json.NewEncoder(w) if environmentName != "" { var runList []string if environmentName == "_default" { runList = chefRole.RunList } else { runList = chefRole.EnvRunLists[environmentName] } resp := make(map[string][]string, 1) resp["run_list"] = runList if err = enc.Encode(&resp); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusBadRequest) } } else { roleEnvs := make([]string, len(chefRole.EnvRunLists)+1) roleEnvs[0] = "_default" i := 1 for k := range chefRole.EnvRunLists { roleEnvs[i] = k i++ } if err = enc.Encode(&roleEnvs); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusBadRequest) } } default: jsonErrorReport(w, r, "Unrecognized method!", http.StatusMethodNotAllowed) } } }
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 clientHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") path := splitPath(r.URL.Path) clientName := path[1] opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID")) if oerr != nil { jsonErrorReport(w, r, oerr.Error(), oerr.Status()) return } switch r.Method { case "DELETE": chefClient, gerr := client.Get(clientName) if gerr != nil { jsonErrorReport(w, r, gerr.Error(), gerr.Status()) return } if !opUser.IsAdmin() && !opUser.IsSelf(chefClient) { jsonErrorReport(w, r, "Deleting that client is forbidden", http.StatusForbidden) return } /* Docs were incorrect. It does want the body of the * deleted object. */ jsonClient := chefClient.ToJSON() /* Log the delete event before deleting the client, in * case the client is deleting itself. */ if lerr := loginfo.LogEvent(opUser, chefClient, "delete"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } err := chefClient.Delete() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusForbidden) return } enc := json.NewEncoder(w) if err = enc.Encode(&jsonClient); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } case "GET": chefClient, gerr := client.Get(clientName) if gerr != nil { jsonErrorReport(w, r, gerr.Error(), gerr.Status()) return } if !opUser.IsAdmin() && !opUser.IsSelf(chefClient) { jsonErrorReport(w, r, "You are not allowed to perform that action.", http.StatusForbidden) return } /* API docs are wrong here re: public_key vs. * certificate. Also orgname (at least w/ open source) * and clientname, and it wants chef_type and * json_class */ jsonClient := chefClient.ToJSON() enc := json.NewEncoder(w) if err := enc.Encode(&jsonClient); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } case "PUT": clientData, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return } chefClient, err := client.Get(clientName) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } /* Makes chef-pedant happy. I suppose it is, after all, * pedantic. */ if averr := util.CheckAdminPlusValidator(clientData); averr != nil { jsonErrorReport(w, r, averr.Error(), averr.Status()) return } if !opUser.IsAdmin() && !opUser.IsSelf(chefClient) { jsonErrorReport(w, r, "You are not allowed to perform that action.", http.StatusForbidden) return } if !opUser.IsAdmin() { var verr util.Gerror aerr := opUser.CheckPermEdit(clientData, "admin") if !opUser.IsValidator() { verr = opUser.CheckPermEdit(clientData, "validator") } if aerr != nil && verr != nil { jsonErrorReport(w, r, "Client can be either an admin or a validator, but not both.", http.StatusBadRequest) return } else if aerr != nil || verr != nil { if aerr == nil { aerr = verr } jsonErrorReport(w, r, aerr.Error(), aerr.Status()) return } } jsonName, sterr := util.ValidateAsString(clientData["name"]) if sterr != nil { jsonErrorReport(w, r, sterr.Error(), http.StatusBadRequest) return } /* If clientName and clientData["name"] aren't the * same, we're renaming. Check the new name doesn't * already exist. */ jsonClient := chefClient.ToJSON() if clientName != jsonName { if lerr := loginfo.LogEvent(opUser, chefClient, "modify"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } err := chefClient.Rename(jsonName) if err != nil { jsonErrorReport(w, r, err.Error(), err.Status()) return } w.WriteHeader(http.StatusCreated) } if uerr := chefClient.UpdateFromJSON(clientData); uerr != nil { jsonErrorReport(w, r, uerr.Error(), uerr.Status()) return } if pk, pkfound := clientData["public_key"]; pkfound { switch pk := pk.(type) { case string: if pkok, pkerr := client.ValidatePublicKey(pk); !pkok { jsonErrorReport(w, r, pkerr.Error(), http.StatusBadRequest) return } chefClient.SetPublicKey(pk) jsonClient["public_key"] = pk case nil: //show_public_key = false default: jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } } if p, pfound := clientData["private_key"]; pfound { switch p := p.(type) { case bool: if p { var cgerr error if jsonClient["private_key"], cgerr = chefClient.GenerateKeys(); cgerr != nil { jsonErrorReport(w, r, cgerr.Error(), http.StatusInternalServerError) return } // make sure the json // client gets the new // public key jsonClient["public_key"] = chefClient.PublicKey() } default: jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } } chefClient.Save() if lerr := loginfo.LogEvent(opUser, chefClient, "modify"); lerr != nil { jsonErrorReport(w, r, lerr.Error(), http.StatusInternalServerError) return } enc := json.NewEncoder(w) if err := enc.Encode(&jsonClient); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } default: jsonErrorReport(w, r, "Unrecognized method for client!", http.StatusMethodNotAllowed) } }
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) } }
// 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 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 sandboxHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") pathArray := splitPath(r.URL.Path) sboxResponse := 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 } switch r.Method { case "POST": if len(pathArray) != 1 { jsonErrorReport(w, r, "Bad request.", http.StatusMethodNotAllowed) return } if !opUser.IsAdmin() { jsonErrorReport(w, r, "You are not allowed to take this action.", http.StatusForbidden) return } jsonReq, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return } sboxHash, ok := jsonReq["checksums"].(map[string]interface{}) if !ok { jsonErrorReport(w, r, "Field 'checksums' missing", http.StatusBadRequest) return } else if len(sboxHash) == 0 { jsonErrorReport(w, r, "Bad checksums!", http.StatusBadRequest) return } else { for _, j := range sboxHash { if j != nil { jsonErrorReport(w, r, "Bad checksums!", http.StatusBadRequest) return } } } sbox, err := sandbox.New(sboxHash) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusBadRequest) return } err = sbox.Save() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } /* If we're here, make the slightly weird response. */ sboxResponse["uri"] = util.ObjURL(sbox) sboxResponse["sandbox_id"] = sbox.ID sboxResponse["checksums"] = sbox.UploadChkList() w.WriteHeader(http.StatusCreated) case "PUT": if len(pathArray) != 2 { jsonErrorReport(w, r, "Bad request.", http.StatusMethodNotAllowed) return } if !opUser.IsAdmin() { jsonErrorReport(w, r, "You are not allowed to take this action.", http.StatusForbidden) return } sandboxID := pathArray[1] jsonReq, jerr := parseObjJSON(r.Body) if jerr != nil { jsonErrorReport(w, r, jerr.Error(), http.StatusBadRequest) return } sboxCommit, ok := jsonReq["is_completed"].(bool) if !ok { jsonErrorReport(w, r, "Bad request", http.StatusBadRequest) return } sbox, err := sandbox.Get(sandboxID) if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusNotFound) return } if err = sbox.IsComplete(); err == nil { sbox.Completed = sboxCommit err = sbox.Save() if err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) return } } else { jsonErrorReport(w, r, err.Error(), http.StatusServiceUnavailable) return } /* The response here is a bit confusing too. The * documented behavior doesn't match with the observed * behavior from chef-zero, and it's not real clear what * it wants from the checksums array. Still, we need to * give it what it wants. Ask about this later. */ sboxResponse["guid"] = sbox.ID sboxResponse["name"] = sbox.ID sboxResponse["is_completed"] = sbox.Completed sboxResponse["create_time"] = sbox.CreationTime.UTC().Format("2006-01-02T15:04:05+00:00") sboxResponse["checksums"] = sbox.Checksums default: jsonErrorReport(w, r, "Unrecognized method!", http.StatusMethodNotAllowed) return } enc := json.NewEncoder(w) if err := enc.Encode(&sboxResponse); err != nil { jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError) } }