github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/admin/client.go (about)

     1  package admin
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"time"
    11  
    12  	"github.com/pyroscope-io/pyroscope/pkg/api"
    13  	"github.com/pyroscope-io/pyroscope/pkg/model/appmetadata"
    14  
    15  	"github.com/hashicorp/go-multierror"
    16  )
    17  
    18  type Client struct {
    19  	httpClient *http.Client
    20  }
    21  
    22  // TODO since this is shared between client/server
    23  // maybe we could share it?
    24  const (
    25  	appsEndpoint    = "http://pyroscope/v1/apps"
    26  	usersEndpoint   = "http://pyroscope/v1/users"
    27  	storageEndpoint = "http://pyroscope/v1/storage"
    28  )
    29  
    30  var (
    31  	ErrHTTPClientCreation = errors.New("failed to create http over uds client")
    32  	ErrMakingRequest      = errors.New("failed to make a request")
    33  	ErrStatusCodeNotOK    = errors.New("failed to get a response with a valid status code")
    34  	ErrDecodingResponse   = errors.New("error while decoding a (assumed) json response")
    35  	ErrMarshalingPayload  = errors.New("error while marshalling the payload")
    36  )
    37  
    38  func NewClient(socketAddr string, timeout time.Duration) (*Client, error) {
    39  	httpClient, err := NewHTTPOverUDSClient(socketAddr, WithTimeout(timeout))
    40  
    41  	if err != nil {
    42  		return nil, multierror.Append(ErrHTTPClientCreation, err)
    43  	}
    44  
    45  	return &Client{
    46  		httpClient,
    47  	}, nil
    48  }
    49  
    50  type AppNames []string
    51  
    52  func (c *Client) GetAppsNames() (names AppNames, err error) {
    53  	resp, err := c.httpClient.Get(appsEndpoint)
    54  	if err != nil {
    55  		return names, multierror.Append(ErrMakingRequest, err)
    56  	}
    57  
    58  	if err := checkStatusCodeOK(resp.StatusCode); err != nil {
    59  		return names, multierror.Append(ErrStatusCodeNotOK, err)
    60  	}
    61  
    62  	// Strip all data except fqname
    63  	var apps []appmetadata.ApplicationMetadata
    64  	err = json.NewDecoder(resp.Body).Decode(&apps)
    65  	if err != nil {
    66  		return names, multierror.Append(ErrDecodingResponse, err)
    67  	}
    68  
    69  	appNames := make([]string, len(apps))
    70  	for i, appName := range apps {
    71  		appNames[i] = appName.FQName
    72  	}
    73  
    74  	return appNames, nil
    75  }
    76  
    77  func (c *Client) DeleteApp(name string) (err error) {
    78  	payload := api.DeleteAppInput{Name: name}
    79  
    80  	marshalledPayload, err := json.Marshal(payload)
    81  	if err != nil {
    82  		return multierror.Append(ErrMarshalingPayload, err)
    83  	}
    84  
    85  	req, err := http.NewRequest(http.MethodDelete, appsEndpoint, bytes.NewBuffer(marshalledPayload))
    86  	if err != nil {
    87  		return fmt.Errorf("error creating request: %w", err)
    88  	}
    89  
    90  	resp, err := c.httpClient.Do(req)
    91  	if err != nil {
    92  		return multierror.Append(ErrMakingRequest, err)
    93  	}
    94  
    95  	err = checkStatusCodeOK(resp.StatusCode)
    96  	if err != nil {
    97  		return multierror.Append(ErrStatusCodeNotOK, err)
    98  	}
    99  	return nil
   100  }
   101  
   102  func (c *Client) ResetUserPassword(username, password string, enable bool) error {
   103  	reqBody := UpdateUserRequest{Password: &password}
   104  	if enable {
   105  		reqBody.SetIsDisabled(false)
   106  	}
   107  
   108  	var b bytes.Buffer
   109  	if err := json.NewEncoder(&b).Encode(reqBody); err != nil {
   110  		return multierror.Append(ErrMarshalingPayload, err)
   111  	}
   112  
   113  	req, err := http.NewRequest(http.MethodPatch, usersEndpoint+"/"+username, &b)
   114  	if err != nil {
   115  		return fmt.Errorf("error creating request: %w", err)
   116  	}
   117  	return c.do(req)
   118  }
   119  
   120  func (c *Client) CleanupStorage() error {
   121  	req, err := http.NewRequest(http.MethodPut, storageEndpoint+"/cleanup", nil)
   122  	if err != nil {
   123  		return fmt.Errorf("error creating request: %w", err)
   124  	}
   125  	return c.do(req)
   126  }
   127  
   128  func (c *Client) do(req *http.Request) error {
   129  	resp, err := c.httpClient.Do(req)
   130  	if err != nil {
   131  		return err
   132  	}
   133  	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
   134  		return nil
   135  	}
   136  	if v, err := io.ReadAll(resp.Body); err == nil {
   137  		return fmt.Errorf(string(v))
   138  	}
   139  	return fmt.Errorf("received non 2xx status code: %d", resp.StatusCode)
   140  }
   141  
   142  func checkStatusCodeOK(statusCode int) error {
   143  	statusOK := statusCode >= 200 && statusCode < 300
   144  	if !statusOK {
   145  		return fmt.Errorf("Received non 2xx status code: %d", statusCode)
   146  	}
   147  	return nil
   148  }