package couchdb import ( "encoding/json" "fmt" "net/http" "net/url" "strconv" "strings" ) // Server represents a CouchDB server instance. type Server struct { resource *Resource } // NewServer creates a CouchDB server instance in address urlStr. func NewServer(urlStr string) (*Server, error) { return newServer(urlStr, true) } // NewServerNoFullCommit creates a CouchDB server instance in address urlStr // with X-Couch-Full-Commit disabled. func NewServerNoFullCommit(urlStr string) (*Server, error) { return newServer(urlStr, false) } func newServer(urlStr string, fullCommit bool) (*Server, error) { res, err := NewResource(urlStr, nil) if err != nil { return nil, err } s := &Server{ resource: res, } if !fullCommit { s.resource.header.Set("X-Couch-Full-Commit", "false") } return s, nil } // Config returns the entire CouchDB server configuration as JSON structure. func (s *Server) Config(node string) (map[string]map[string]string, error) { _, data, err := s.resource.GetJSON(fmt.Sprintf("_node/%s/_config", node), nil, nil) if err != nil { return nil, err } var config map[string]map[string]string err = json.Unmarshal(data, &config) if err != nil { return nil, err } return config, nil } // Version returns the version info about CouchDB instance. func (s *Server) Version() (string, error) { var jsonMap map[string]interface{} _, data, err := s.resource.GetJSON("", nil, nil) if err != nil { return "", err } err = json.Unmarshal(data, &jsonMap) if err != nil { return "", err } return jsonMap["version"].(string), nil } func (s *Server) String() string { return fmt.Sprintf("Server %s", s.resource.base) } // ActiveTasks lists of running tasks. func (s *Server) ActiveTasks() ([]interface{}, error) { var tasks []interface{} _, data, err := s.resource.GetJSON("_active_tasks", nil, nil) if err != nil { return nil, err } err = json.Unmarshal(data, &tasks) if err != nil { return nil, err } return tasks, nil } // DBs returns a list of all the databases in the CouchDB server instance. func (s *Server) DBs() ([]string, error) { var dbs []string _, data, err := s.resource.GetJSON("_all_dbs", nil, nil) if err != nil { return nil, err } err = json.Unmarshal(data, &dbs) if err != nil { return nil, err } return dbs, nil } // Stats returns a JSON object containing the statistics for the running server. func (s *Server) Stats(node, entry string) (map[string]interface{}, error) { var stats map[string]interface{} _, data, err := s.resource.GetJSON(fmt.Sprintf("_node/%s/_stats/%s", node, entry), nil, url.Values{}) if err != nil { return nil, err } err = json.Unmarshal(data, &stats) if err != nil { return nil, err } return stats, nil } // Len returns the number of dbs in CouchDB server instance. func (s *Server) Len() (int, error) { dbs, err := s.DBs() if err != nil { return -1, err } return len(dbs), nil } // Create returns a database instance with the given name, returns true if created, // if database already existed, returns false, *Database will be nil if failed. func (s *Server) Create(name string) (*Database, error) { _, _, err := s.resource.PutJSON(name, nil, nil, nil) // ErrPreconditionFailed means database with the given name already existed if err != nil && err != ErrPreconditionFailed { return nil, err } db, getErr := s.Get(name) if getErr != nil { return nil, getErr } return db, err } // Delete deletes a database with the given name. Return false if failed. func (s *Server) Delete(name string) error { _, _, err := s.resource.DeleteJSON(name, nil, nil) return err } // Get gets a database instance with the given name. Return nil if failed. func (s *Server) Get(name string) (*Database, error) { res, err := s.resource.NewResourceWithURL(name) if err != nil { return nil, err } db, err := NewDatabaseWithResource(res) if err != nil { return nil, err } _, _, err = db.resource.Head("", nil, nil) if err != nil { return nil, err } return db, nil } // Contains returns true if a db with given name exsited. func (s *Server) Contains(name string) bool { _, _, err := s.resource.Head(name, nil, nil) return err == nil } // Membership displays the nodes that are part of the cluster as clusterNodes. // The field allNodes displays all nodes this node knows about, including the // ones that are part of cluster. func (s *Server) Membership() ([]string, []string, error) { var jsonMap map[string]*json.RawMessage _, data, err := s.resource.GetJSON("_membership", nil, nil) if err != nil { return nil, nil, err } err = json.Unmarshal(data, &jsonMap) if err != nil { return nil, nil, err } var allNodes []string var clusterNodes []string err = json.Unmarshal(*jsonMap["all_nodes"], &allNodes) if err != nil { return nil, nil, err } err = json.Unmarshal(*jsonMap["cluster_nodes"], &clusterNodes) if err != nil { return nil, nil, err } return allNodes, clusterNodes, nil } // Replicate requests, configure or stop a replication operation. func (s *Server) Replicate(source, target string, options map[string]interface{}) (map[string]interface{}, error) { var result map[string]interface{} body := map[string]interface{}{ "source": source, "target": target, } if options != nil { for k, v := range options { body[k] = v } } _, data, err := s.resource.PostJSON("_replicate", nil, body, nil) if err != nil { return nil, err } json.Unmarshal(data, &result) return result, nil } // UUIDs requests one or more Universally Unique Identifiers from the CouchDB instance. // The response is a JSON object providing a list of UUIDs. // count - Number of UUIDs to return. Default is 1. func (s *Server) UUIDs(count int) ([]string, error) { if count <= 0 { count = 1 } values := url.Values{} values.Set("count", strconv.Itoa(count)) _, data, err := s.resource.GetJSON("_uuids", nil, values) if err != nil { return nil, err } var jsonMap map[string]*json.RawMessage err = json.Unmarshal(data, &jsonMap) if err != nil { return nil, err } var uuids []string err = json.Unmarshal(*jsonMap["uuids"], &uuids) if err != nil { return nil, err } return uuids, nil } // newResource returns an url string representing a resource under server. func (s *Server) newResource(resource string) string { resourceURL, err := s.resource.base.Parse(resource) if err != nil { return "" } return resourceURL.String() } // AddUser adds regular user in authentication database. // Returns id and rev of the registered user. func (s *Server) AddUser(name, password string, roles []string) (string, string, error) { var id, rev string db, err := s.Get("_users") if err != nil { return "", "", err } if roles == nil { roles = []string{} } userDoc := map[string]interface{}{ "_id": "org.couchdb.user:" + name, "name": name, "password": password, "roles": roles, "type": "user", } id, rev, err = db.Save(userDoc, nil) if err != nil { return id, rev, err } return id, rev, nil } // Login regular user in CouchDB, returns authentication token. func (s *Server) Login(name, password string) (string, error) { body := map[string]interface{}{ "name": name, "password": password, } header, _, err := s.resource.PostJSON("_session", nil, body, nil) if err != nil { return "", err } tokenPart := strings.Split(header.Get("Set-Cookie"), ";")[0] token := strings.Split(tokenPart, "=")[1] return token, err } // VerifyToken returns error if user's token is invalid. func (s *Server) VerifyToken(token string) error { header := http.Header{} header.Set("Cookie", strings.Join([]string{"AuthSession", token}, "=")) _, _, err := s.resource.GetJSON("_session", header, nil) return err } // Logout regular user in CouchDB func (s *Server) Logout(token string) error { header := http.Header{} header.Set("Cookie", strings.Join([]string{"AuthSession", token}, "=")) _, _, err := s.resource.DeleteJSON("_session", header, nil) return err } // RemoveUser removes regular user in authentication database. func (s *Server) RemoveUser(name string) error { db, err := s.Get("_users") if err != nil { return err } docID := "org.couchdb.user:" + name return db.Delete(docID) }