github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/edp/client.go (about)

     1  package edp
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"time"
    11  
    12  	kebError "github.com/kyma-project/kyma-environment-broker/internal/error"
    13  
    14  	"github.com/sirupsen/logrus"
    15  	"golang.org/x/oauth2/clientcredentials"
    16  )
    17  
    18  const (
    19  	MaasConsumerEnvironmentKey = "maasConsumerEnvironment"
    20  	MaasConsumerRegionKey      = "maasConsumerRegion"
    21  	MaasConsumerSubAccountKey  = "maasConsumerSubAccount"
    22  	MaasConsumerServicePlan    = "maasConsumerServicePlan"
    23  
    24  	dataTenantTmpl     = "%s/namespaces/%s/dataTenants"
    25  	metadataTenantTmpl = "%s/namespaces/%s/dataTenants/%s/%s/metadata"
    26  
    27  	namespaceToken = "%s/oauth2/token"
    28  )
    29  
    30  type Config struct {
    31  	AuthURL     string
    32  	AdminURL    string
    33  	Namespace   string
    34  	Secret      string
    35  	Environment string `envconfig:"default=prod"`
    36  	Required    bool   `envconfig:"default=false"`
    37  	Disabled    bool
    38  }
    39  
    40  type Client struct {
    41  	config     Config
    42  	httpClient *http.Client
    43  	log        logrus.FieldLogger
    44  }
    45  
    46  func NewClient(config Config, log logrus.FieldLogger) *Client {
    47  	cfg := clientcredentials.Config{
    48  		ClientID:     fmt.Sprintf("edp-namespace;%s", config.Namespace),
    49  		ClientSecret: config.Secret,
    50  		TokenURL:     fmt.Sprintf(namespaceToken, config.AuthURL),
    51  		Scopes:       []string{"edp-namespace.read edp-namespace.update"},
    52  	}
    53  	httpClientOAuth := cfg.Client(context.Background())
    54  	httpClientOAuth.Timeout = 30 * time.Second
    55  
    56  	return &Client{
    57  		config:     config,
    58  		httpClient: httpClientOAuth,
    59  		log:        log,
    60  	}
    61  }
    62  
    63  func (c *Client) dataTenantURL() string {
    64  	return fmt.Sprintf(dataTenantTmpl, c.config.AdminURL, c.config.Namespace)
    65  }
    66  
    67  func (c *Client) metadataTenantURL(name, env string) string {
    68  	return fmt.Sprintf(metadataTenantTmpl, c.config.AdminURL, c.config.Namespace, name, env)
    69  }
    70  
    71  func (c *Client) CreateDataTenant(data DataTenantPayload) error {
    72  	rawData, err := json.Marshal(data)
    73  	if err != nil {
    74  		return fmt.Errorf("while marshaling dataTenant payload: %w", err)
    75  	}
    76  
    77  	return c.post(c.dataTenantURL(), rawData, data.Name)
    78  }
    79  
    80  func (c *Client) DeleteDataTenant(name, env string) (err error) {
    81  	URL := fmt.Sprintf("%s/%s/%s", c.dataTenantURL(), name, env)
    82  	request, err := http.NewRequest(http.MethodDelete, URL, nil)
    83  	if err != nil {
    84  		return fmt.Errorf("while creating delete dataTenant request: %w", err)
    85  	}
    86  
    87  	response, err := c.httpClient.Do(request)
    88  	defer func() {
    89  		if closeErr := c.closeResponseBody(response); closeErr != nil {
    90  			err = kebError.AsTemporaryError(closeErr, "while closing delete DataTenant response")
    91  		}
    92  	}()
    93  	if err != nil {
    94  		return kebError.AsTemporaryError(err, "while requesting about delete dataTenant")
    95  	}
    96  
    97  	return c.processResponse(response, true, name)
    98  }
    99  
   100  func (c *Client) CreateMetadataTenant(name, env string, data MetadataTenantPayload) error {
   101  	rawData, err := json.Marshal(data)
   102  	if err != nil {
   103  		return fmt.Errorf("while marshaling tenant metadata payload: %w", err)
   104  	}
   105  
   106  	return c.post(c.metadataTenantURL(name, env), rawData, name)
   107  }
   108  
   109  func (c *Client) DeleteMetadataTenant(name, env, key string) (err error) {
   110  	URL := fmt.Sprintf("%s/%s", c.metadataTenantURL(name, env), key)
   111  	request, err := http.NewRequest(http.MethodDelete, URL, nil)
   112  	if err != nil {
   113  		return fmt.Errorf("while creating delete metadata request: %w", err)
   114  	}
   115  
   116  	response, err := c.httpClient.Do(request)
   117  	defer func() {
   118  		if closeErr := c.closeResponseBody(response); closeErr != nil {
   119  			err = kebError.AsTemporaryError(closeErr, "while closing delete MetadataTenant response")
   120  		}
   121  	}()
   122  	if err != nil {
   123  		return kebError.AsTemporaryError(err, "while requesting about delete metadata")
   124  	}
   125  
   126  	return c.processResponse(response, true, name)
   127  }
   128  
   129  func (c *Client) GetMetadataTenant(name, env string) (_ []MetadataItem, err error) {
   130  	var metadata []MetadataItem
   131  	request, err := http.NewRequest(http.MethodGet, c.metadataTenantURL(name, env), nil)
   132  	if err != nil {
   133  		return metadata, fmt.Errorf("while creating GET metadata tenant request: %w", err)
   134  	}
   135  
   136  	response, err := c.httpClient.Do(request)
   137  	defer func() {
   138  		if closeErr := c.closeResponseBody(response); closeErr != nil {
   139  			err = kebError.AsTemporaryError(closeErr, "while closing get MetadataTenant response")
   140  		}
   141  	}()
   142  	if err != nil {
   143  		return metadata, kebError.AsTemporaryError(err, "while requesting about dataTenant metadata")
   144  	}
   145  
   146  	err = json.NewDecoder(response.Body).Decode(&metadata)
   147  	if err != nil {
   148  		return metadata, fmt.Errorf("while decoding dataTenant metadata response: %w", err)
   149  	}
   150  
   151  	return metadata, nil
   152  }
   153  
   154  func (c *Client) post(URL string, data []byte, id string) (err error) {
   155  	request, err := http.NewRequest(http.MethodPost, URL, bytes.NewBuffer(data))
   156  	if err != nil {
   157  		return fmt.Errorf("while creating POST request for %s: %w", URL, err)
   158  	}
   159  	request.Header.Set("Content-Type", "application/json")
   160  
   161  	response, err := c.httpClient.Do(request)
   162  	defer func() {
   163  		if closeErr := c.closeResponseBody(response); closeErr != nil {
   164  			err = kebError.AsTemporaryError(closeErr, "while closing post response")
   165  		}
   166  	}()
   167  	if err != nil {
   168  		return kebError.AsTemporaryError(err, "while sending POST request on %s", URL)
   169  	}
   170  
   171  	return c.processResponse(response, false, id)
   172  }
   173  
   174  func (c *Client) processResponse(response *http.Response, allowNotFound bool, id string) error {
   175  	byteBody, err := ioutil.ReadAll(response.Body)
   176  	if err != nil {
   177  		return fmt.Errorf("while reading response body (status code %d): %w", response.StatusCode, err)
   178  	}
   179  	body := string(byteBody)
   180  
   181  	switch response.StatusCode {
   182  	case http.StatusCreated:
   183  		c.log.Infof("Resource created: %s", responseLog(response))
   184  		return nil
   185  	case http.StatusConflict:
   186  		c.log.Warnf("Resource already exist: %s", responseLog(response))
   187  		return NewEDPConflictError(id, "Resource %s already exists", id)
   188  	case http.StatusNoContent:
   189  		c.log.Infof("Action executed correctly: %s", responseLog(response))
   190  		return nil
   191  	case http.StatusNotFound:
   192  		c.log.Infof("Resource not found: %s", responseLog(response))
   193  		if allowNotFound {
   194  			return nil
   195  		}
   196  		c.log.Errorf("Body content: %s", body)
   197  		return NewEDPNotFoundError(id, "Not Found: %s", responseLog(response))
   198  	case http.StatusRequestTimeout:
   199  		c.log.Errorf("Request timeout %s: %s", responseLog(response), body)
   200  		return kebError.WrapNewTemporaryError(NewEDPOtherError(id, http.StatusRequestTimeout, "Request timeout: %s", responseLog(response)))
   201  	case http.StatusBadRequest:
   202  		c.log.Errorf("Bad request %s: %s", responseLog(response), body)
   203  		return NewEDPBadRequestError(id, "Bad request: %s", responseLog(response))
   204  	}
   205  
   206  	if response.StatusCode >= 500 {
   207  		c.log.Errorf("EDP server returns failed status %s: %s", responseLog(response), body)
   208  		return kebError.WrapNewTemporaryError(NewEDPOtherError(id, response.StatusCode, "EDP server returns failed status %s", responseLog(response)))
   209  	}
   210  
   211  	c.log.Errorf("EDP server not supported response %s: %s", responseLog(response), body)
   212  	return NewEDPOtherError(id, response.StatusCode, "Undefined/empty/notsupported status code response %s", responseLog(response))
   213  }
   214  
   215  func responseLog(r *http.Response) string {
   216  	return fmt.Sprintf("Response status code: %d for request %s %s", r.StatusCode, r.Request.Method, r.Request.URL)
   217  }
   218  
   219  func (c *Client) closeResponseBody(response *http.Response) error {
   220  	if response == nil {
   221  		return nil
   222  	}
   223  	if response.Body == nil {
   224  		return nil
   225  	}
   226  	return response.Body.Close()
   227  }