package client import ( "context" "encoding/json" "fmt" "net/http" "net/url" "strings" "time" "github.com/gorilla/schema" "github.com/porter-dev/porter/api/types" "github.com/porter-dev/porter/provisioner/pb" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) type Client struct { BaseURL string Token string TokenID uint HTTPClient *http.Client GRPCClient pb.ProvisionerClient conn *grpc.ClientConn } func NewClient(baseURL, token string, tokenID uint) (*Client, error) { parsedURL, err := url.Parse(baseURL) if err != nil { return nil, err } conn, err := grpc.Dial(parsedURL.Host, grpc.WithInsecure()) if err != nil { return nil, err } gClient := pb.NewProvisionerClient(conn) client := &Client{ BaseURL: baseURL, Token: token, TokenID: tokenID, HTTPClient: &http.Client{ Timeout: time.Minute, }, GRPCClient: gClient, conn: conn, } return client, nil } func (c *Client) NewGRPCContext(workspaceID string) (context.Context, context.CancelFunc) { headers := map[string]string{ "workspace_id": workspaceID, "token": c.Token, } if c.TokenID != 0 { headers["token_id"] = fmt.Sprintf("%d", c.TokenID) } header := metadata.New(headers) ctx := metadata.NewOutgoingContext(context.Background(), header) return context.WithCancel(ctx) } func (c *Client) CloseConnection() error { return c.conn.Close() } func (c *Client) getRequest(relPath string, data interface{}, response interface{}) error { vals := make(map[string][]string) err := schema.NewEncoder().Encode(data, vals) urlVals := url.Values(vals) encodedURLVals := urlVals.Encode() var req *http.Request if encodedURLVals != "" { req, err = http.NewRequest( "GET", fmt.Sprintf("%s%s?%s", c.BaseURL, relPath, encodedURLVals), nil, ) } else { req, err = http.NewRequest( "GET", fmt.Sprintf("%s%s", c.BaseURL, relPath), nil, ) } if err != nil { return err } if httpErr, err := c.sendRequest(req, response); httpErr != nil || err != nil { if httpErr != nil { return fmt.Errorf("%v", httpErr.Error) } return err } return nil } type postRequestOpts struct { retryCount uint } func (c *Client) postRequest(relPath string, data interface{}, response interface{}, opts ...postRequestOpts) error { var retryCount uint = 1 if len(opts) > 0 { for _, opt := range opts { retryCount = opt.retryCount } } var httpErr *types.ExternalError var err error for i := 0; i < int(retryCount); i++ { strData, err := json.Marshal(data) if err != nil { return nil } req, err := http.NewRequest( "POST", fmt.Sprintf("%s%s", c.BaseURL, relPath), strings.NewReader(string(strData)), ) if err != nil { return err } httpErr, err = c.sendRequest(req, response) if httpErr == nil && err == nil { return nil } if i != int(retryCount)-1 { if httpErr != nil { fmt.Printf("Error: %s (status code %d), retrying request...\n", httpErr.Error, httpErr.Code) } else { fmt.Printf("Error: %v, retrying request...\n", err) } } } if httpErr != nil { return fmt.Errorf("%v", httpErr.Error) } return err } func (c *Client) deleteRequest(relPath string, data interface{}, response interface{}) error { strData, err := json.Marshal(data) if err != nil { return nil } req, err := http.NewRequest( "DELETE", fmt.Sprintf("%s%s", c.BaseURL, relPath), strings.NewReader(string(strData)), ) if err != nil { return err } if httpErr, err := c.sendRequest(req, response); httpErr != nil || err != nil { if httpErr != nil { return fmt.Errorf("%v", httpErr.Error) } return err } return nil } func (c *Client) sendRequest(req *http.Request, v interface{}) (*types.ExternalError, error) { req.Header.Set("Content-Type", "application/json; charset=utf-8") req.Header.Set("Accept", "application/json; charset=utf-8") if c.Token != "" && c.TokenID != 0 { req.Header.Set("X-Porter-Token", c.Token) req.Header.Set("X-Porter-Token-ID", fmt.Sprintf("%d", c.TokenID)) } else if c.Token != "" { req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.Token)) } res, err := c.HTTPClient.Do(req) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest { var errRes types.ExternalError if err = json.NewDecoder(res.Body).Decode(&errRes); err == nil { return &errRes, nil } return nil, fmt.Errorf("unknown error, status code: %d", res.StatusCode) } if v != nil { if err = json.NewDecoder(res.Body).Decode(v); err != nil { return nil, err } } return nil, nil }