package emgql import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "net/url" "time" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.uber.org/zap" "go.equinixmetal.net/infra9-metal-bridge/internal/metal/providers" ) const ( defaultHTTPTimeout = 5 * time.Second ) // DefaultHTTPClient is the default http client used if no client is provided. var DefaultHTTPClient = &http.Client{ Timeout: defaultHTTPTimeout, Transport: otelhttp.NewTransport(http.DefaultTransport), } var _ providers.Provider = &Client{} // Client is the client to interact with the equinix metal graphql service. type Client struct { logger *zap.SugaredLogger httpClient *http.Client baseURL *url.URL } // New creates a new emapi client func New(options ...Option) (*Client, error) { client := &Client{} for _, opt := range options { if err := opt(client); err != nil { return nil, err } } if client.logger == nil { client.logger = zap.NewNop().Sugar() } if client.httpClient == nil { client.httpClient = DefaultHTTPClient } return client, nil } func (c *Client) graphquery(ctx context.Context, q string, vars map[string]string) (io.ReadCloser, error) { qObj := struct { Query string `json:"query"` Variables map[string]string `json:"variables"` }{ Query: q, Variables: vars, } res, err := json.Marshal(qObj) if err != nil { return nil, fmt.Errorf("failed to marshal request object: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL.String(), bytes.NewBuffer(res)) if err != nil { return nil, fmt.Errorf("failed to build post request: %w", err) } req.Header.Add("content-type", "application/json") resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("failed to make post request: %w", err) } if resp.StatusCode != http.StatusOK { b, err2 := io.ReadAll(resp.Body) if err2 != nil { return nil, fmt.Errorf("got unexpected response code [%d]: unable to parse request body: %v", resp.StatusCode, err) } return nil, fmt.Errorf("got unexpected response code [%d]: %s", resp.StatusCode, string(b)) } return resp.Body, nil }