github.com/projectdiscovery/nuclei/v2@v2.9.15/internal/runner/nucleicloud/cloud.go (about)

     1  package nucleicloud
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"mime/multipart"
    10  	"net/http"
    11  	"net/url"
    12  	"os"
    13  	"path/filepath"
    14  	"strconv"
    15  	"strings"
    16  	"time"
    17  
    18  	jsoniter "github.com/json-iterator/go"
    19  	"github.com/pkg/errors"
    20  	"github.com/projectdiscovery/nuclei/v2/pkg/output"
    21  	"github.com/projectdiscovery/retryablehttp-go"
    22  )
    23  
    24  // Client is a client for result retrieval from nuclei-cloud API
    25  type Client struct {
    26  	baseURL    string
    27  	apiKey     string
    28  	httpclient *retryablehttp.Client
    29  }
    30  
    31  const (
    32  	pollInterval   = 3 * time.Second
    33  	resultSize     = 100
    34  	defaultBaseURL = "https://cloud-dev.nuclei.sh"
    35  )
    36  
    37  // HTTPErrorRetryPolicy is to retry for HTTPCodes >= 500.
    38  func HTTPErrorRetryPolicy() func(ctx context.Context, resp *http.Response, err error) (bool, error) {
    39  	return func(ctx context.Context, resp *http.Response, err error) (bool, error) {
    40  		if resp != nil && resp.StatusCode >= http.StatusInternalServerError {
    41  			return true, errors.New(resp.Status)
    42  		}
    43  		return retryablehttp.CheckRecoverableErrors(ctx, resp, err)
    44  	}
    45  }
    46  
    47  // New returns a nuclei-cloud API client
    48  func New(baseURL, apiKey string) *Client {
    49  	options := retryablehttp.DefaultOptionsSingle
    50  	options.NoAdjustTimeout = true
    51  	options.Timeout = 60 * time.Second
    52  	options.CheckRetry = HTTPErrorRetryPolicy()
    53  	client := retryablehttp.NewClient(options)
    54  
    55  	baseAppURL := baseURL
    56  	if baseAppURL == "" {
    57  		baseAppURL = defaultBaseURL
    58  	}
    59  	return &Client{httpclient: client, baseURL: baseAppURL, apiKey: apiKey}
    60  }
    61  
    62  // AddScan adds a scan for templates and target to nuclei server
    63  func (c *Client) AddScan(req *AddScanRequest) (int64, error) {
    64  	var buf bytes.Buffer
    65  	if err := jsoniter.NewEncoder(&buf).Encode(req); err != nil {
    66  		return 0, errors.Wrap(err, "could not encode request")
    67  	}
    68  	httpReq, err := retryablehttp.NewRequest(http.MethodPost, fmt.Sprintf("%s/scan", c.baseURL), bytes.NewReader(buf.Bytes()))
    69  	if err != nil {
    70  		return 0, errors.Wrap(err, "could not make request")
    71  	}
    72  
    73  	resp, err := c.sendRequest(httpReq)
    74  	if err != nil {
    75  		return 0, errors.Wrap(err, "could not do request")
    76  	}
    77  	defer resp.Body.Close()
    78  
    79  	var data map[string]int64
    80  	if err := jsoniter.NewDecoder(resp.Body).Decode(&data); err != nil {
    81  		return 0, errors.Wrap(err, "could not decode resp")
    82  	}
    83  	id := data["id"]
    84  	return id, nil
    85  }
    86  
    87  // GetResults gets results from nuclei server for an ID
    88  // until there are no more results left to retrieve.
    89  func (c *Client) GetResults(ID int64, checkProgress bool, limit int, callback func(*output.ResultEvent)) error {
    90  	lastID := int64(0)
    91  
    92  	for {
    93  		uri := fmt.Sprintf("%s/results?id=%d&from=%d&size=%d", c.baseURL, ID, lastID, limit)
    94  		httpReq, err := retryablehttp.NewRequest(http.MethodGet, uri, nil)
    95  		if err != nil {
    96  			return errors.Wrap(err, "could not make request")
    97  		}
    98  
    99  		resp, err := c.sendRequest(httpReq)
   100  		if err != nil {
   101  			return errors.Wrap(err, "could not do request")
   102  		}
   103  
   104  		var items GetResultsResponse
   105  		if err := jsoniter.NewDecoder(resp.Body).Decode(&items); err != nil {
   106  			resp.Body.Close()
   107  			return errors.Wrap(err, "could not decode results")
   108  		}
   109  		resp.Body.Close()
   110  
   111  		for _, item := range items.Items {
   112  			lastID = item.ID
   113  
   114  			var result output.ResultEvent
   115  			if err := jsoniter.NewDecoder(strings.NewReader(item.Raw)).Decode(&result); err != nil {
   116  				return errors.Wrap(err, "could not decode result item")
   117  			}
   118  			callback(&result)
   119  		}
   120  
   121  		// This is checked during scan is added else if no item found break out of loop.
   122  		if checkProgress {
   123  			if items.Finished && len(items.Items) == 0 {
   124  				break
   125  			}
   126  		} else if len(items.Items) == 0 {
   127  			break
   128  		}
   129  
   130  		time.Sleep(pollInterval)
   131  	}
   132  	return nil
   133  }
   134  
   135  func (c *Client) GetScans(limit int, from string) ([]GetScanRequest, error) {
   136  	var items []GetScanRequest
   137  	httpReq, err := retryablehttp.NewRequest(http.MethodGet, fmt.Sprintf("%s/scan?from=%s&size=%d", c.baseURL, url.QueryEscape(from), limit), nil)
   138  	if err != nil {
   139  		return items, errors.Wrap(err, "could not make request")
   140  	}
   141  
   142  	resp, err := c.sendRequest(httpReq)
   143  	if err != nil {
   144  		return nil, errors.Wrap(err, "could not do request")
   145  	}
   146  	defer resp.Body.Close()
   147  
   148  	if err := jsoniter.NewDecoder(resp.Body).Decode(&items); err != nil {
   149  		return items, errors.Wrap(err, "could not decode results")
   150  	}
   151  	return items, nil
   152  }
   153  
   154  func (c *Client) GetScan(id int64) (GetScanRequest, error) {
   155  	var items GetScanRequest
   156  	httpReq, err := retryablehttp.NewRequest(http.MethodGet, fmt.Sprintf("%s/scan/%d", c.baseURL, id), nil)
   157  	if err != nil {
   158  		return items, errors.Wrap(err, "could not make request")
   159  	}
   160  
   161  	resp, err := c.sendRequest(httpReq)
   162  	if err != nil {
   163  		return items, errors.Wrap(err, "could not do request")
   164  	}
   165  	defer resp.Body.Close()
   166  
   167  	if err := jsoniter.NewDecoder(resp.Body).Decode(&items); err != nil {
   168  		return items, errors.Wrap(err, "could not decode results")
   169  	}
   170  	return items, nil
   171  }
   172  
   173  // Delete a scan and it's issues by the scan id.
   174  func (c *Client) DeleteScan(id int64) (DeleteScanResults, error) {
   175  	deletescan := DeleteScanResults{}
   176  	httpReq, err := retryablehttp.NewRequest(http.MethodDelete, fmt.Sprintf("%s/scan?id=%d", c.baseURL, id), nil)
   177  	if err != nil {
   178  		return deletescan, errors.Wrap(err, "could not make request")
   179  	}
   180  
   181  	resp, err := c.sendRequest(httpReq)
   182  	if err != nil {
   183  		return deletescan, errors.Wrap(err, "could not do request")
   184  	}
   185  	defer resp.Body.Close()
   186  
   187  	if err := jsoniter.NewDecoder(resp.Body).Decode(&deletescan); err != nil {
   188  		return deletescan, errors.Wrap(err, "could not delete scan")
   189  	}
   190  	return deletescan, nil
   191  }
   192  
   193  // StatusDataSource returns the status for a data source
   194  func (c *Client) StatusDataSource(statusRequest StatusDataSourceRequest) (int64, error) {
   195  	var buf bytes.Buffer
   196  	if err := jsoniter.NewEncoder(&buf).Encode(statusRequest); err != nil {
   197  		return 0, errors.Wrap(err, "could not encode request")
   198  	}
   199  	httpReq, err := retryablehttp.NewRequest(http.MethodPost, fmt.Sprintf("%s/datasources/status", c.baseURL), bytes.NewReader(buf.Bytes()))
   200  	if err != nil {
   201  		return 0, errors.Wrap(err, "could not make request")
   202  	}
   203  
   204  	resp, err := c.sendRequest(httpReq)
   205  	if err != nil {
   206  		return 0, errors.Wrap(err, "could not do request")
   207  	}
   208  	defer resp.Body.Close()
   209  
   210  	var data StatusDataSourceResponse
   211  	if err := jsoniter.NewDecoder(resp.Body).Decode(&data); err != nil {
   212  		return 0, errors.Wrap(err, "could not decode resp")
   213  	}
   214  	return data.ID, nil
   215  }
   216  
   217  // AddDataSource adds a new data source
   218  func (c *Client) AddDataSource(req AddDataSourceRequest) (*AddDataSourceResponse, error) {
   219  	var buf bytes.Buffer
   220  	if err := jsoniter.NewEncoder(&buf).Encode(req); err != nil {
   221  		return nil, errors.Wrap(err, "could not encode request")
   222  	}
   223  	httpReq, err := retryablehttp.NewRequest(http.MethodPost, fmt.Sprintf("%s/datasources", c.baseURL), bytes.NewReader(buf.Bytes()))
   224  	if err != nil {
   225  		return nil, errors.Wrap(err, "could not make request")
   226  	}
   227  	resp, err := c.sendRequest(httpReq)
   228  	if err != nil {
   229  		return nil, errors.Wrap(err, "could not do request")
   230  	}
   231  	defer resp.Body.Close()
   232  
   233  	var data AddDataSourceResponse
   234  	if err := jsoniter.NewDecoder(resp.Body).Decode(&data); err != nil {
   235  		return nil, errors.Wrap(err, "could not decode resp")
   236  	}
   237  	return &data, nil
   238  }
   239  
   240  // SyncDataSource syncs contents for a data source. The call blocks until
   241  // update is completed.
   242  func (c *Client) SyncDataSource(ID int64) error {
   243  	httpReq, err := retryablehttp.NewRequest(http.MethodGet, fmt.Sprintf("%s/datasources/%d/sync", c.baseURL, ID), nil)
   244  	if err != nil {
   245  		return errors.Wrap(err, "could not make request")
   246  	}
   247  
   248  	resp, err := c.sendRequest(httpReq)
   249  	if err != nil {
   250  		return errors.Wrap(err, "could not do request")
   251  	}
   252  	defer resp.Body.Close()
   253  	_, _ = io.Copy(io.Discard, resp.Body)
   254  	return nil
   255  }
   256  
   257  // ExistsDataSourceItem identifies whether data source item exist
   258  func (c *Client) ExistsDataSourceItem(req ExistsDataSourceItemRequest) error {
   259  	var buf bytes.Buffer
   260  	if err := jsoniter.NewEncoder(&buf).Encode(req); err != nil {
   261  		return errors.Wrap(err, "could not encode request")
   262  	}
   263  	httpReq, err := retryablehttp.NewRequest(http.MethodPost, fmt.Sprintf("%s/datasources/exists", c.baseURL), bytes.NewReader(buf.Bytes()))
   264  	if err != nil {
   265  		return errors.Wrap(err, "could not make request")
   266  	}
   267  	resp, err := c.sendRequest(httpReq)
   268  	if err != nil {
   269  		return errors.Wrap(err, "could not do request")
   270  	}
   271  	defer resp.Body.Close()
   272  	_, _ = io.Copy(io.Discard, resp.Body)
   273  	return nil
   274  }
   275  
   276  func (c *Client) ListDatasources() ([]GetDataSourceResponse, error) {
   277  	var items []GetDataSourceResponse
   278  	httpReq, err := retryablehttp.NewRequest(http.MethodGet, fmt.Sprintf("%s/datasources", c.baseURL), nil)
   279  	if err != nil {
   280  		return items, errors.Wrap(err, "could not make request")
   281  	}
   282  
   283  	resp, err := c.sendRequest(httpReq)
   284  	if err != nil {
   285  		return nil, errors.Wrap(err, "could not do request")
   286  	}
   287  	defer resp.Body.Close()
   288  
   289  	if err := jsoniter.NewDecoder(resp.Body).Decode(&items); err != nil {
   290  		return items, errors.Wrap(err, "could not decode results")
   291  	}
   292  	return items, nil
   293  }
   294  
   295  func (c *Client) ListReportingSources() ([]GetReportingSourceResponse, error) {
   296  	var items []GetReportingSourceResponse
   297  	httpReq, err := retryablehttp.NewRequest(http.MethodGet, fmt.Sprintf("%s/reporting", c.baseURL), nil)
   298  	if err != nil {
   299  		return items, errors.Wrap(err, "could not make request")
   300  	}
   301  
   302  	resp, err := c.sendRequest(httpReq)
   303  	if err != nil {
   304  		return nil, errors.Wrap(err, "could not do request")
   305  	}
   306  	defer resp.Body.Close()
   307  
   308  	if err := jsoniter.NewDecoder(resp.Body).Decode(&items); err != nil {
   309  		return items, errors.Wrap(err, "could not decode results")
   310  	}
   311  	return items, nil
   312  }
   313  
   314  func (c *Client) ToggleReportingSource(ID int64, status bool) error {
   315  	r := ReportingSourceStatus{Enabled: status}
   316  
   317  	var buf bytes.Buffer
   318  	if err := jsoniter.NewEncoder(&buf).Encode(r); err != nil {
   319  		return errors.Wrap(err, "could not encode request")
   320  	}
   321  	httpReq, err := retryablehttp.NewRequest(http.MethodPut, fmt.Sprintf("%s/reporting/%d", c.baseURL, ID), bytes.NewReader(buf.Bytes()))
   322  	if err != nil {
   323  		return errors.Wrap(err, "could not make request")
   324  	}
   325  
   326  	resp, err := c.sendRequest(httpReq)
   327  	if err != nil {
   328  		return errors.Wrap(err, "could not do request")
   329  	}
   330  	defer resp.Body.Close()
   331  	_, _ = io.Copy(io.Discard, resp.Body)
   332  	return nil
   333  }
   334  
   335  func (c *Client) ListTargets(query string) ([]GetTargetResponse, error) {
   336  	var builder strings.Builder
   337  	_, _ = builder.WriteString(c.baseURL)
   338  	_, _ = builder.WriteString("/targets")
   339  	if query != "" {
   340  		_, _ = builder.WriteString("?query=")
   341  		_, _ = builder.WriteString(url.QueryEscape(query))
   342  	}
   343  
   344  	var items []GetTargetResponse
   345  	httpReq, err := retryablehttp.NewRequest(http.MethodGet, builder.String(), nil)
   346  	if err != nil {
   347  		return items, errors.Wrap(err, "could not make request")
   348  	}
   349  
   350  	resp, err := c.sendRequest(httpReq)
   351  	if err != nil {
   352  		return nil, errors.Wrap(err, "could not do request")
   353  	}
   354  	defer resp.Body.Close()
   355  
   356  	if err := jsoniter.NewDecoder(resp.Body).Decode(&items); err != nil {
   357  		return items, errors.Wrap(err, "could not decode results")
   358  	}
   359  	return items, nil
   360  }
   361  
   362  func (c *Client) ListTemplates(query string) ([]GetTemplatesResponse, error) {
   363  	var builder strings.Builder
   364  	_, _ = builder.WriteString(c.baseURL)
   365  	_, _ = builder.WriteString("/templates")
   366  	if query != "" {
   367  		_, _ = builder.WriteString("?query=")
   368  		_, _ = builder.WriteString(url.QueryEscape(query))
   369  	}
   370  
   371  	var items []GetTemplatesResponse
   372  	httpReq, err := retryablehttp.NewRequest(http.MethodGet, builder.String(), nil)
   373  	if err != nil {
   374  		return items, errors.Wrap(err, "could not make request")
   375  	}
   376  
   377  	resp, err := c.sendRequest(httpReq)
   378  	if err != nil {
   379  		return nil, errors.Wrap(err, "could not do request")
   380  	}
   381  	defer resp.Body.Close()
   382  
   383  	if err := jsoniter.NewDecoder(resp.Body).Decode(&items); err != nil {
   384  		return items, errors.Wrap(err, "could not decode results")
   385  	}
   386  	return items, nil
   387  }
   388  
   389  func (c *Client) RemoveDatasource(datasource int64, name string) error {
   390  	var builder strings.Builder
   391  	_, _ = builder.WriteString(c.baseURL)
   392  	_, _ = builder.WriteString("/datasources")
   393  
   394  	if name != "" {
   395  		_, _ = builder.WriteString("?name=")
   396  		_, _ = builder.WriteString(name)
   397  	} else if datasource != 0 {
   398  		_, _ = builder.WriteString("?id=")
   399  		_, _ = builder.WriteString(strconv.FormatInt(datasource, 10))
   400  	}
   401  
   402  	httpReq, err := retryablehttp.NewRequest(http.MethodDelete, builder.String(), nil)
   403  	if err != nil {
   404  		return errors.Wrap(err, "could not make request")
   405  	}
   406  
   407  	resp, err := c.sendRequest(httpReq)
   408  	if err != nil {
   409  		return errors.Wrap(err, "could not do request")
   410  	}
   411  	defer resp.Body.Close()
   412  	_, _ = io.Copy(io.Discard, resp.Body)
   413  	return nil
   414  }
   415  
   416  func (c *Client) AddTemplate(name, contents string) (string, error) {
   417  	file, err := os.Open(contents)
   418  	if err != nil {
   419  		return "", errors.Wrap(err, "could not open contents")
   420  	}
   421  	defer file.Close()
   422  
   423  	var buf bytes.Buffer
   424  	writer := multipart.NewWriter(&buf)
   425  	_ = writer.WriteField("name", name)
   426  	fileWriter, _ := writer.CreateFormFile("file", filepath.Base(contents))
   427  	_, _ = io.Copy(fileWriter, file)
   428  	_ = writer.Close()
   429  
   430  	httpReq, err := retryablehttp.NewRequest(http.MethodPost, fmt.Sprintf("%s/templates", c.baseURL), &buf)
   431  	if err != nil {
   432  		return "", errors.Wrap(err, "could not make request")
   433  	}
   434  	httpReq.Header.Set("Content-Type", writer.FormDataContentType())
   435  
   436  	resp, err := c.sendRequest(httpReq)
   437  	if err != nil {
   438  		return "", errors.Wrap(err, "could not do request")
   439  	}
   440  	defer resp.Body.Close()
   441  
   442  	var item AddItemResponse
   443  	if err := jsoniter.NewDecoder(resp.Body).Decode(&item); err != nil {
   444  		return "", errors.Wrap(err, "could not decode results")
   445  	}
   446  	return item.Ok, nil
   447  }
   448  
   449  func (c *Client) AddTarget(name, contents string) (string, error) {
   450  	file, err := os.Open(contents)
   451  	if err != nil {
   452  		return "", errors.Wrap(err, "could not open contents")
   453  	}
   454  	defer file.Close()
   455  
   456  	var buf bytes.Buffer
   457  	writer := multipart.NewWriter(&buf)
   458  	_ = writer.WriteField("name", name)
   459  	fileWriter, _ := writer.CreateFormFile("file", filepath.Base(contents))
   460  	_, _ = io.Copy(fileWriter, file)
   461  	_ = writer.Close()
   462  
   463  	httpReq, err := retryablehttp.NewRequest(http.MethodPost, fmt.Sprintf("%s/targets", c.baseURL), &buf)
   464  	if err != nil {
   465  		return "", errors.Wrap(err, "could not make request")
   466  	}
   467  	httpReq.Header.Set("Content-Type", writer.FormDataContentType())
   468  
   469  	resp, err := c.sendRequest(httpReq)
   470  	if err != nil {
   471  		return "", errors.Wrap(err, "could not do request")
   472  	}
   473  	defer resp.Body.Close()
   474  
   475  	var item AddItemResponse
   476  	if err := jsoniter.NewDecoder(resp.Body).Decode(&item); err != nil {
   477  		return "", errors.Wrap(err, "could not decode results")
   478  	}
   479  	return item.Ok, nil
   480  }
   481  
   482  func (c *Client) RemoveTemplate(ID int64, name string) error {
   483  	var builder strings.Builder
   484  	_, _ = builder.WriteString(c.baseURL)
   485  	_, _ = builder.WriteString("/templates")
   486  
   487  	if name != "" {
   488  		_, _ = builder.WriteString("?name=")
   489  		_, _ = builder.WriteString(name)
   490  	} else if ID != 0 {
   491  		_, _ = builder.WriteString("?id=")
   492  		_, _ = builder.WriteString(strconv.FormatInt(ID, 10))
   493  	}
   494  	httpReq, err := retryablehttp.NewRequest(http.MethodDelete, builder.String(), nil)
   495  	if err != nil {
   496  		return errors.Wrap(err, "could not make request")
   497  	}
   498  
   499  	resp, err := c.sendRequest(httpReq)
   500  	if err != nil {
   501  		return errors.Wrap(err, "could not do request")
   502  	}
   503  	defer resp.Body.Close()
   504  	_, _ = io.Copy(io.Discard, resp.Body)
   505  	return nil
   506  }
   507  
   508  func (c *Client) RemoveTarget(ID int64, name string) error {
   509  	var builder strings.Builder
   510  	_, _ = builder.WriteString(c.baseURL)
   511  	_, _ = builder.WriteString("/targets")
   512  
   513  	if name != "" {
   514  		_, _ = builder.WriteString("?name=")
   515  		_, _ = builder.WriteString(name)
   516  	} else if ID != 0 {
   517  		_, _ = builder.WriteString("?id=")
   518  		_, _ = builder.WriteString(strconv.FormatInt(ID, 10))
   519  	}
   520  	httpReq, err := retryablehttp.NewRequest(http.MethodDelete, builder.String(), nil)
   521  	if err != nil {
   522  		return errors.Wrap(err, "could not make request")
   523  	}
   524  
   525  	resp, err := c.sendRequest(httpReq)
   526  	if err != nil {
   527  		return errors.Wrap(err, "could not do request")
   528  	}
   529  	defer resp.Body.Close()
   530  	_, _ = io.Copy(io.Discard, resp.Body)
   531  	return nil
   532  }
   533  
   534  func (c *Client) GetTarget(ID int64, name string) (io.ReadCloser, error) {
   535  	var builder strings.Builder
   536  	_, _ = builder.WriteString(c.baseURL)
   537  	_, _ = builder.WriteString("/targets/get")
   538  
   539  	if name != "" {
   540  		_, _ = builder.WriteString("?name=")
   541  		_, _ = builder.WriteString(name)
   542  	} else if ID != 0 {
   543  		_, _ = builder.WriteString("?id=")
   544  		_, _ = builder.WriteString(strconv.FormatInt(ID, 10))
   545  	}
   546  	httpReq, err := retryablehttp.NewRequest(http.MethodGet, builder.String(), nil)
   547  	if err != nil {
   548  		return nil, errors.Wrap(err, "could not make request")
   549  	}
   550  
   551  	resp, err := c.sendRequest(httpReq)
   552  	if err != nil {
   553  		return nil, errors.Wrap(err, "could not do request")
   554  	}
   555  	return resp.Body, nil
   556  }
   557  
   558  func (c *Client) GetTemplate(ID int64, name string) (io.ReadCloser, error) {
   559  	var builder strings.Builder
   560  	_, _ = builder.WriteString(c.baseURL)
   561  	_, _ = builder.WriteString("/templates/get")
   562  
   563  	if name != "" {
   564  		_, _ = builder.WriteString("?name=")
   565  		_, _ = builder.WriteString(name)
   566  	} else if ID != 0 {
   567  		_, _ = builder.WriteString("?id=")
   568  		_, _ = builder.WriteString(strconv.FormatInt(ID, 10))
   569  	}
   570  	httpReq, err := retryablehttp.NewRequest(http.MethodGet, builder.String(), nil)
   571  	if err != nil {
   572  		return nil, errors.Wrap(err, "could not make request")
   573  	}
   574  
   575  	resp, err := c.sendRequest(httpReq)
   576  	if err != nil {
   577  		return nil, errors.Wrap(err, "could not do request")
   578  	}
   579  	return resp.Body, nil
   580  }
   581  
   582  func (c *Client) ExistsTarget(id int64) (ExistsInputResponse, error) {
   583  	var item ExistsInputResponse
   584  	httpReq, err := retryablehttp.NewRequest(http.MethodGet, fmt.Sprintf("%s/targets/%d/exists", c.baseURL, id), nil)
   585  	if err != nil {
   586  		return item, errors.Wrap(err, "could not make request")
   587  	}
   588  
   589  	resp, err := c.sendRequest(httpReq)
   590  	if err != nil {
   591  		return item, errors.Wrap(err, "could not do request")
   592  	}
   593  	defer resp.Body.Close()
   594  
   595  	if err := jsoniter.NewDecoder(resp.Body).Decode(&item); err != nil {
   596  		return item, errors.Wrap(err, "could not decode results")
   597  	}
   598  	return item, nil
   599  }
   600  
   601  func (c *Client) ExistsTemplate(id int64) (ExistsInputResponse, error) {
   602  	var item ExistsInputResponse
   603  	httpReq, err := retryablehttp.NewRequest(http.MethodGet, fmt.Sprintf("%s/templates/%d/exists", c.baseURL, id), nil)
   604  	if err != nil {
   605  		return item, errors.Wrap(err, "could not make request")
   606  	}
   607  
   608  	resp, err := c.sendRequest(httpReq)
   609  	if err != nil {
   610  		return item, errors.Wrap(err, "could not do request")
   611  	}
   612  	defer resp.Body.Close()
   613  
   614  	if err := jsoniter.NewDecoder(resp.Body).Decode(&item); err != nil {
   615  		return item, errors.Wrap(err, "could not decode results")
   616  	}
   617  	return item, nil
   618  }
   619  
   620  const apiKeyParameter = "X-API-Key"
   621  
   622  type errorResponse struct {
   623  	Message string `json:"message"`
   624  }
   625  
   626  func (c *Client) sendRequest(req *retryablehttp.Request) (*http.Response, error) {
   627  	req.Header.Set(apiKeyParameter, c.apiKey)
   628  
   629  	resp, err := c.httpclient.Do(req)
   630  	if err != nil {
   631  		return nil, errors.Wrap(err, "could not do request")
   632  	}
   633  	if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest {
   634  		data, _ := io.ReadAll(resp.Body)
   635  		resp.Body.Close()
   636  		var errRes errorResponse
   637  		if err = json.NewDecoder(bytes.NewReader(data)).Decode(&errRes); err == nil {
   638  			return nil, errors.New(errRes.Message)
   639  		}
   640  		return nil, fmt.Errorf("unknown error, status code: %d=%s", resp.StatusCode, string(data))
   641  	}
   642  	return resp, nil
   643  }
   644  
   645  // AddReportingSource adds a new data source
   646  func (c *Client) AddReportingSource(req AddReportingSourceRequest) (*AddReportingSourceResponse, error) {
   647  	var buf bytes.Buffer
   648  	if err := jsoniter.NewEncoder(&buf).Encode(req); err != nil {
   649  		return nil, errors.Wrap(err, "could not encode request")
   650  	}
   651  	httpReq, err := retryablehttp.NewRequest(http.MethodPost, fmt.Sprintf("%s/reporting/add-source", c.baseURL), bytes.NewReader(buf.Bytes()))
   652  	if err != nil {
   653  		return nil, errors.Wrap(err, "could not make request")
   654  	}
   655  	resp, err := c.sendRequest(httpReq)
   656  	if err != nil {
   657  		return nil, errors.Wrap(err, "could not do request")
   658  	}
   659  	defer resp.Body.Close()
   660  
   661  	var data AddReportingSourceResponse
   662  	if err := jsoniter.NewDecoder(resp.Body).Decode(&data); err != nil {
   663  		return nil, errors.Wrap(err, "could not decode resp")
   664  	}
   665  	return &data, nil
   666  }