open-vault/vendor/go.mongodb.org/atlas/mongodbatlas/mongodbatlas.go
Michael Golowka bd79fbafb3
Add couchbase, elasticsearch, and mongodbatlas back (#10222)
Updated the `Serve` function so these can be added back into Vault
2020-10-22 17:20:17 -06:00

476 lines
15 KiB
Go

package mongodbatlas // import "go.mongodb.org/atlas/mongodbatlas"
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"reflect"
"runtime"
"strconv"
"strings"
"github.com/google/go-querystring/query"
)
const (
defaultBaseURL = "https://cloud.mongodb.com/api/atlas/v1.0/"
jsonMediaType = "application/json"
gzipMediaType = "application/gzip"
libraryName = "go-mongodbatlas"
// Version the version of the current API client
Version = "0.5.0" // Should be set to the next version planned to be released
)
var (
userAgent = fmt.Sprintf("%s/%s (%s;%s)", libraryName, Version, runtime.GOOS, runtime.GOARCH)
)
// Doer basic interface of a client to be able to do a request
type Doer interface {
Do(context.Context, *http.Request, interface{}) (*Response, error)
}
// Completer interface for clients with callback
type Completer interface {
OnRequestCompleted(RequestCompletionCallback)
}
// RequestDoer minimum interface for any service of the client
type RequestDoer interface {
Doer
Completer
NewRequest(context.Context, string, string, interface{}) (*http.Request, error)
}
// GZipRequestDoer minimum interface for any service of the client that should handle gzip downloads
type GZipRequestDoer interface {
Doer
Completer
NewGZipRequest(context.Context, string, string) (*http.Request, error)
}
// Client manages communication with MongoDBAtlas v1.0 API
type Client struct {
client *http.Client
BaseURL *url.URL
UserAgent string
// Services used for communicating with the API
CustomDBRoles CustomDBRolesService
DatabaseUsers DatabaseUsersService
ProjectIPWhitelist ProjectIPWhitelistService
ProjectIPAccessList ProjectIPAccessListService
Organizations OrganizationsService
Projects ProjectsService
Clusters ClustersService
CloudProviderSnapshots CloudProviderSnapshotsService
APIKeys APIKeysService
ProjectAPIKeys ProjectAPIKeysService
CloudProviderSnapshotRestoreJobs CloudProviderSnapshotRestoreJobsService
Peers PeersService
Containers ContainersService
EncryptionsAtRest EncryptionsAtRestService
WhitelistAPIKeys WhitelistAPIKeysService
PrivateIPMode PrivateIPModeService
MaintenanceWindows MaintenanceWindowsService
Teams TeamsService
AtlasUsers AtlasUsersService
GlobalClusters GlobalClustersService
Auditing AuditingsService
AlertConfigurations AlertConfigurationsService
PrivateEndpoints PrivateEndpointsService
X509AuthDBUsers X509AuthDBUsersService
ContinuousSnapshots ContinuousSnapshotsService
ContinuousRestoreJobs ContinuousRestoreJobsService
Checkpoints CheckpointsService
Alerts AlertsService
CloudProviderSnapshotBackupPolicies CloudProviderSnapshotBackupPoliciesService
Events EventsService
Processes ProcessesService
ProcessMeasurements ProcessMeasurementsService
ProcessDisks ProcessDisksService
ProcessDiskMeasurements ProcessDiskMeasurementsService
ProcessDatabases ProcessDatabasesService
ProcessDatabaseMeasurements ProcessDatabaseMeasurementsService
Indexes IndexesService
Logs LogsService
DataLakes DataLakeService
OnlineArchives OnlineArchiveService
Search SearchService
CustomAWSDNS AWSCustomDNSService
Integrations IntegrationsService
LDAPConfigurations LDAPConfigurationsService
PerformanceAdvisor PerformanceAdvisorService
onRequestCompleted RequestCompletionCallback
}
// RequestCompletionCallback defines the type of the request callback function
type RequestCompletionCallback func(*http.Request, *http.Response)
type service struct {
Client RequestDoer
}
// Response is a MongoDBAtlas response. This wraps the standard http.Response returned from MongoDBAtlas API.
type Response struct {
*http.Response
// Links that were returned with the response.
Links []*Link `json:"links"`
}
// ListOptions specifies the optional parameters to List methods that
// support pagination.
type ListOptions struct {
// For paginated result sets, page of results to retrieve.
PageNum int `url:"pageNum,omitempty"`
// For paginated result sets, the number of results to include per page.
ItemsPerPage int `url:"itemsPerPage,omitempty"`
}
// ErrorResponse reports the error caused by an API request.
type ErrorResponse struct {
// HTTP response that caused this error
Response *http.Response
// The error code, which is simply the HTTP status code.
ErrorCode int `json:"Error"`
// A short description of the error, which is simply the HTTP status phrase.
Reason string `json:"reason"`
// A more detailed description of the error.
Detail string `json:"detail,omitempty"`
}
func (resp *Response) getCurrentPageLink() (*Link, error) {
if link := resp.getLinkByRef("self"); link != nil {
return link, nil
}
return nil, errors.New("no self link found")
}
func (resp *Response) getLinkByRef(ref string) *Link {
for i := range resp.Links {
if resp.Links[i].Rel == ref {
return resp.Links[i]
}
}
return nil
}
// IsLastPage returns true if the current page is the last page
func (resp *Response) IsLastPage() bool {
return resp.getLinkByRef("next") == nil
}
// CurrentPage gets the current page for list pagination request.
func (resp *Response) CurrentPage() (int, error) {
link, err := resp.getCurrentPageLink()
if err != nil {
return 0, err
}
pageNumStr, err := link.getHrefQueryParam("pageNum")
if err != nil {
return 0, err
}
pageNum, err := strconv.Atoi(pageNumStr)
if err != nil {
return 0, fmt.Errorf("error getting current page: %s", err)
}
return pageNum, nil
}
// NewClient returns a new MongoDBAtlas API Client
func NewClient(httpClient *http.Client) *Client {
if httpClient == nil {
httpClient = http.DefaultClient
}
baseURL, _ := url.Parse(defaultBaseURL)
c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent}
c.APIKeys = &APIKeysServiceOp{Client: c}
c.CloudProviderSnapshots = &CloudProviderSnapshotsServiceOp{Client: c}
c.ContinuousSnapshots = &ContinuousSnapshotsServiceOp{Client: c}
c.CloudProviderSnapshotRestoreJobs = &CloudProviderSnapshotRestoreJobsServiceOp{Client: c}
c.Clusters = &ClustersServiceOp{Client: c}
c.Containers = &ContainersServiceOp{Client: c}
c.CustomDBRoles = &CustomDBRolesServiceOp{Client: c}
c.DatabaseUsers = &DatabaseUsersServiceOp{Client: c}
c.EncryptionsAtRest = &EncryptionsAtRestServiceOp{Client: c}
c.Organizations = &OrganizationsServiceOp{Client: c}
c.Projects = &ProjectsServiceOp{Client: c}
c.ProjectAPIKeys = &ProjectAPIKeysOp{Client: c}
c.Peers = &PeersServiceOp{Client: c}
c.ProjectIPWhitelist = &ProjectIPWhitelistServiceOp{Client: c}
c.ProjectIPAccessList = &ProjectIPAccessListServiceOp{Client: c}
c.WhitelistAPIKeys = &WhitelistAPIKeysServiceOp{Client: c}
c.PrivateIPMode = &PrivateIPModeServiceOp{Client: c}
c.MaintenanceWindows = &MaintenanceWindowsServiceOp{Client: c}
c.Teams = &TeamsServiceOp{Client: c}
c.AtlasUsers = &AtlasUsersServiceOp{Client: c}
c.GlobalClusters = &GlobalClustersServiceOp{Client: c}
c.Auditing = &AuditingsServiceOp{Client: c}
c.AlertConfigurations = &AlertConfigurationsServiceOp{Client: c}
c.PrivateEndpoints = &PrivateEndpointsServiceOp{Client: c}
c.X509AuthDBUsers = &X509AuthDBUsersServiceOp{Client: c}
c.ContinuousRestoreJobs = &ContinuousRestoreJobsServiceOp{Client: c}
c.Checkpoints = &CheckpointsServiceOp{Client: c}
c.Alerts = &AlertsServiceOp{Client: c}
c.CloudProviderSnapshotBackupPolicies = &CloudProviderSnapshotBackupPoliciesServiceOp{Client: c}
c.Events = &EventsServiceOp{Client: c}
c.Processes = &ProcessesServiceOp{Client: c}
c.ProcessMeasurements = &ProcessMeasurementsServiceOp{Client: c}
c.ProcessDisks = &ProcessDisksServiceOp{Client: c}
c.ProcessDiskMeasurements = &ProcessDiskMeasurementsServiceOp{Client: c}
c.ProcessDatabases = &ProcessDatabasesServiceOp{Client: c}
c.ProcessDatabaseMeasurements = &ProcessDatabaseMeasurementsServiceOp{Client: c}
c.Indexes = &IndexesServiceOp{Client: c}
c.Logs = &LogsServiceOp{Client: c}
c.DataLakes = &DataLakeServiceOp{Client: c}
c.OnlineArchives = &OnlineArchiveServiceOp{Client: c}
c.Search = &SearchServiceOp{Client: c}
c.CustomAWSDNS = &AWSCustomDNSServiceOp{Client: c}
c.Integrations = &IntegrationsServiceOp{Client: c}
c.LDAPConfigurations = &LDAPConfigurationsServiceOp{Client: c}
c.PerformanceAdvisor = &PerformanceAdvisorServiceOp{Client: c}
return c
}
// ClientOpt are options for New.
type ClientOpt func(*Client) error
// New returns a new MongoDBAtlas API client instance.
func New(httpClient *http.Client, opts ...ClientOpt) (*Client, error) {
c := NewClient(httpClient)
for _, opt := range opts {
if err := opt(c); err != nil {
return nil, err
}
}
return c, nil
}
// SetBaseURL is a client option for setting the base URL.
func SetBaseURL(bu string) ClientOpt {
return func(c *Client) error {
u, err := url.Parse(bu)
if err != nil {
return err
}
c.BaseURL = u
return nil
}
}
// SetUserAgent is a client option for setting the user agent.
func SetUserAgent(ua string) ClientOpt {
return func(c *Client) error {
c.UserAgent = fmt.Sprintf("%s %s", ua, userAgent)
return nil
}
}
// NewRequest creates an API request. A relative URL can be provided in urlStr, which will be resolved to the
// BaseURL of the Client. Relative URLS should always be specified without a preceding slash. If specified, the
// value pointed to by body is JSON encoded and included in as the request body.
func (c *Client) NewRequest(ctx context.Context, method, urlStr string, body interface{}) (*http.Request, error) {
if !strings.HasSuffix(c.BaseURL.Path, "/") {
return nil, fmt.Errorf("base URL must have a trailing slash, but %q does not", c.BaseURL)
}
u, err := c.BaseURL.Parse(urlStr)
if err != nil {
return nil, err
}
var buf io.Reader
if body != nil {
if buf, err = c.newEncodedBody(body); err != nil {
return nil, err
}
}
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, err
}
if body != nil {
req.Header.Set("Content-Type", jsonMediaType)
}
req.Header.Add("Accept", jsonMediaType)
if c.UserAgent != "" {
req.Header.Set("User-Agent", c.UserAgent)
}
return req, nil
}
// newEncodedBody returns an ReadWriter object containing the body of the http request
func (c *Client) newEncodedBody(body interface{}) (io.Reader, error) {
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
err := enc.Encode(body)
return buf, err
}
// NewGZipRequest creates an API request that accepts gzip. A relative URL can be provided in urlStr, which will be resolved to the
// BaseURL of the Client. Relative URLS should always be specified without a preceding slash.
func (c *Client) NewGZipRequest(ctx context.Context, method, urlStr string) (*http.Request, error) {
if !strings.HasSuffix(c.BaseURL.Path, "/") {
return nil, fmt.Errorf("base URL must have a trailing slash, but %q does not", c.BaseURL)
}
rel, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
u := c.BaseURL.ResolveReference(rel)
req, err := http.NewRequest(method, u.String(), nil)
if err != nil {
return nil, err
}
req.Header.Add("Accept", gzipMediaType)
if c.UserAgent != "" {
req.Header.Set("User-Agent", c.UserAgent)
}
return req, nil
}
// OnRequestCompleted sets the DO API request completion callback
func (c *Client) OnRequestCompleted(rc RequestCompletionCallback) {
c.onRequestCompleted = rc
}
// Do sends an API request and returns the API response. The API response is JSON decoded and stored in the value
// pointed to by v, or returned as an error if an API error has occurred. If v implements the io.Writer interface,
// the raw response will be written to v, without attempting to decode it.
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
resp, err := DoRequestWithClient(ctx, c.client, req)
if err != nil {
// If we got an error, and the context has been canceled,
// the context's error is probably more useful.
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
return nil, err
}
if c.onRequestCompleted != nil {
c.onRequestCompleted(req, resp)
}
defer func() {
if rerr := resp.Body.Close(); err == nil {
err = rerr
}
}()
response := &Response{Response: resp}
err = CheckResponse(resp)
if err != nil {
return response, err
}
if v != nil {
if w, ok := v.(io.Writer); ok {
_, err = io.Copy(w, resp.Body)
if err != nil {
return nil, err
}
} else {
decErr := json.NewDecoder(resp.Body).Decode(v)
if decErr == io.EOF {
decErr = nil // ignore EOF errors caused by empty response body
}
if decErr != nil {
err = decErr
}
}
}
return response, err
}
func (r *ErrorResponse) Error() string {
return fmt.Sprintf("%v %v: %d (request %q) %v",
r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, r.Reason, r.Detail)
}
// CheckResponse checks the API response for errors, and returns them if present. A response is considered an
// error if it has a status code outside the 200 range. API error responses are expected to have either no response
// body, or a JSON response body that maps to ErrorResponse. Any other response body will be silently ignored.
func CheckResponse(r *http.Response) error {
if c := r.StatusCode; c >= 200 && c <= 299 {
return nil
}
errorResponse := &ErrorResponse{Response: r}
data, err := ioutil.ReadAll(r.Body)
if err == nil && len(data) > 0 {
err := json.Unmarshal(data, errorResponse)
if err != nil {
log.Printf("[DEBUG] unmarshal error response: %s", err)
errorResponse.Reason = string(data)
}
}
return errorResponse
}
// DoRequestWithClient submits an HTTP request using the specified client.
func DoRequestWithClient(
ctx context.Context,
client *http.Client,
req *http.Request) (*http.Response, error) {
req = req.WithContext(ctx)
return client.Do(req)
}
func setListOptions(s string, opt interface{}) (string, error) {
v := reflect.ValueOf(opt)
if v.Kind() == reflect.Ptr && v.IsNil() {
return s, nil
}
origURL, err := url.Parse(s)
if err != nil {
return s, err
}
origValues := origURL.Query()
newValues, err := query.Values(opt)
if err != nil {
return s, err
}
for k, v := range newValues {
origValues[k] = v
}
origURL.RawQuery = origValues.Encode()
return origURL.String(), nil
}