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 }