mirror of
https://codeberg.org/forgejo/forgejo
synced 2024-12-05 02:54:46 +01:00
289 lines
5.9 KiB
Go
289 lines
5.9 KiB
Go
|
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
|
||
|
}
|