Beispiel #1
0
// 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
}
Beispiel #2
0
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
}
Beispiel #3
0
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
}
Beispiel #4
0
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
}
Beispiel #5
0
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)
	}
}
Beispiel #6
0
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)
	}
}
Beispiel #7
0
// 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
	}
}
Beispiel #8
0
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
	}
	// TODO: get a default sort in, once sorting is sorted out.
	if s, found := r.Form["sort"]; found {
		if len(s) > 0 {
			sortOrder = s[0]
		} else {
			sortOrder = "id ASC"
		}
	}
	_ = sortOrder
	if st, found := r.Form["start"]; found {
		if len(st) > 0 {
			start, _ = strconv.Atoi(st[0])
		}
	} else {
		start = 0
	}

	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 := search.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
			}
			/* 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]
			rObjs, err := search.Search(idx, paramQuery)

			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
			}

			res := make([]map[string]interface{}, len(rObjs))
			for i, r := range rObjs {
				switch r := r.(type) {
				case *client.Client:
					jc := map[string]interface{}{
						"name":       r.Name,
						"chef_type":  r.ChefType,
						"json_class": r.JSONClass,
						"admin":      r.Admin,
						"public_key": r.PublicKey(),
						"validator":  r.Validator,
					}
					res[i] = jc
				default:
					res[i] = util.MapifyObject(r)
				}
			}

			/* If we're doing partial search, tease out the
			 * fields we want. */
			if r.Method == "POST" {
				res, err = partialSearchFormat(res, partialData)
				if err != nil {
					jsonErrorReport(w, r, err.Error(), http.StatusBadRequest)
					return
				}
				for x, z := range res {
					tmpRes := make(map[string]interface{})
					switch ro := rObjs[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(rObjs[x].(util.GoiardiObj))
					}
					tmpRes["data"] = z

					res[x] = tmpRes
				}
			}

			end := start + paramsRows
			if end > len(res) {
				end = len(res)
			}
			res = res[start:end]
			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)
	}
}
Beispiel #9
0
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 {
			jsonErrorReport(w, r, err.Error(), http.StatusNotFound)
			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, err := cookbook.DependsCookbooks(cbVer["run_list"].([]string), env.CookbookVersions)
			if err != nil {
				jsonErrorReport(w, r, err.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)
	}
}
Beispiel #10
0
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
		}
		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
}
Beispiel #11
0
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
}
Beispiel #12
0
// 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
}
Beispiel #13
0
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
}