package couchbase import ( "bytes" "encoding/json" "fmt" "github.com/couchbase/goutils/logging" "io/ioutil" "net/http" ) // ViewDefinition represents a single view within a design document. type ViewDefinition struct { Map string `json:"map"` Reduce string `json:"reduce,omitempty"` } // DDoc is the document body of a design document specifying a view. type DDoc struct { Language string `json:"language,omitempty"` Views map[string]ViewDefinition `json:"views"` } // DDocsResult represents the result from listing the design // documents. type DDocsResult struct { Rows []struct { DDoc struct { Meta map[string]interface{} JSON DDoc } `json:"doc"` } `json:"rows"` } // GetDDocs lists all design documents func (b *Bucket) GetDDocs() (DDocsResult, error) { var ddocsResult DDocsResult b.RLock() pool := b.pool uri := b.DDocs.URI b.RUnlock() // MB-23555 ephemeral buckets have no ddocs if uri == "" { return DDocsResult{}, nil } err := pool.client.parseURLResponse(uri, &ddocsResult) if err != nil { return DDocsResult{}, err } return ddocsResult, nil } func (b *Bucket) GetDDocWithRetry(docname string, into interface{}) error { ddocURI := fmt.Sprintf("/%s/_design/%s", b.GetName(), docname) err := b.parseAPIResponse(ddocURI, &into) if err != nil { return err } return nil } func (b *Bucket) GetDDocsWithRetry() (DDocsResult, error) { var ddocsResult DDocsResult b.RLock() uri := b.DDocs.URI b.RUnlock() // MB-23555 ephemeral buckets have no ddocs if uri == "" { return DDocsResult{}, nil } err := b.parseURLResponse(uri, &ddocsResult) if err != nil { return DDocsResult{}, err } return ddocsResult, nil } func (b *Bucket) ddocURL(docname string) (string, error) { u, err := b.randomBaseURL() if err != nil { return "", err } u.Path = fmt.Sprintf("/%s/_design/%s", b.GetName(), docname) return u.String(), nil } func (b *Bucket) ddocURLNext(nodeId int, docname string) (string, int, error) { u, selected, err := b.randomNextURL(nodeId) if err != nil { return "", -1, err } u.Path = fmt.Sprintf("/%s/_design/%s", b.GetName(), docname) return u.String(), selected, nil } const ABS_MAX_RETRIES = 10 const ABS_MIN_RETRIES = 3 func (b *Bucket) getMaxRetries() (int, error) { maxRetries := len(b.Nodes()) if maxRetries == 0 { return 0, fmt.Errorf("No available Couch rest URLs") } if maxRetries > ABS_MAX_RETRIES { maxRetries = ABS_MAX_RETRIES } else if maxRetries < ABS_MIN_RETRIES { maxRetries = ABS_MIN_RETRIES } return maxRetries, nil } // PutDDoc installs a design document. func (b *Bucket) PutDDoc(docname string, value interface{}) error { var Err error maxRetries, err := b.getMaxRetries() if err != nil { return err } lastNode := START_NODE_ID for retryCount := 0; retryCount < maxRetries; retryCount++ { Err = nil ddocU, selectedNode, err := b.ddocURLNext(lastNode, docname) if err != nil { return err } lastNode = selectedNode logging.Infof(" Trying with selected node %d", selectedNode) j, err := json.Marshal(value) if err != nil { return err } req, err := http.NewRequest("PUT", ddocU, bytes.NewReader(j)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") err = maybeAddAuth(req, b.authHandler(false /* bucket not yet locked */)) if err != nil { return err } res, err := doHTTPRequest(req) if err != nil { return err } if res.StatusCode != 201 { body, _ := ioutil.ReadAll(res.Body) Err = fmt.Errorf("error installing view: %v / %s", res.Status, body) logging.Errorf(" Error in PutDDOC %v. Retrying...", Err) res.Body.Close() b.Refresh() continue } res.Body.Close() break } return Err } // GetDDoc retrieves a specific a design doc. func (b *Bucket) GetDDoc(docname string, into interface{}) error { var Err error var res *http.Response maxRetries, err := b.getMaxRetries() if err != nil { return err } lastNode := START_NODE_ID for retryCount := 0; retryCount < maxRetries; retryCount++ { Err = nil ddocU, selectedNode, err := b.ddocURLNext(lastNode, docname) if err != nil { return err } lastNode = selectedNode logging.Infof(" Trying with selected node %d", selectedNode) req, err := http.NewRequest("GET", ddocU, nil) if err != nil { return err } req.Header.Set("Content-Type", "application/json") err = maybeAddAuth(req, b.authHandler(false /* bucket not yet locked */)) if err != nil { return err } res, err = doHTTPRequest(req) if err != nil { return err } if res.StatusCode != 200 { body, _ := ioutil.ReadAll(res.Body) Err = fmt.Errorf("error reading view: %v / %s", res.Status, body) logging.Errorf(" Error in GetDDOC %v Retrying...", Err) b.Refresh() res.Body.Close() continue } defer res.Body.Close() break } if Err != nil { return Err } d := json.NewDecoder(res.Body) return d.Decode(into) } // DeleteDDoc removes a design document. func (b *Bucket) DeleteDDoc(docname string) error { var Err error maxRetries, err := b.getMaxRetries() if err != nil { return err } lastNode := START_NODE_ID for retryCount := 0; retryCount < maxRetries; retryCount++ { Err = nil ddocU, selectedNode, err := b.ddocURLNext(lastNode, docname) if err != nil { return err } lastNode = selectedNode logging.Infof(" Trying with selected node %d", selectedNode) req, err := http.NewRequest("DELETE", ddocU, nil) if err != nil { return err } req.Header.Set("Content-Type", "application/json") err = maybeAddAuth(req, b.authHandler(false /* bucket not already locked */)) if err != nil { return err } res, err := doHTTPRequest(req) if err != nil { return err } if res.StatusCode != 200 { body, _ := ioutil.ReadAll(res.Body) Err = fmt.Errorf("error deleting view : %v / %s", res.Status, body) logging.Errorf(" Error in DeleteDDOC %v. Retrying ... ", Err) b.Refresh() res.Body.Close() continue } res.Body.Close() break } return Err }