Skip to content

Commit

Permalink
Merge pull request #227 from cpontes-ns1/PENG-3689
Browse files Browse the repository at this point in the history
PENG-3689 - Introducing support for datasets functionality
  • Loading branch information
shane-ns1 authored Feb 14, 2024
2 parents 4ed51d2 + 6de884b commit b4aa3a8
Show file tree
Hide file tree
Showing 9 changed files with 871 additions and 12 deletions.
61 changes: 61 additions & 0 deletions mockns1/datasets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package mockns1

import (
"net/http"

"gopkg.in/ns1/ns1-go.v2/rest/model/dataset"
)

// AddDatasetListTestCase sets up a test case for the api.Client.Datasets.List() function
func (s *Service) AddDatasetListTestCase(
requestHeaders, responseHeaders http.Header,
response []*dataset.Dataset,
) error {
return s.AddTestCase(
http.MethodGet, "/datasets", http.StatusOK, requestHeaders,
responseHeaders, "", response,
)
}

// AddDatasetGetTestCase sets up a test case for the api.Client.Datasets.Get() function
func (s *Service) AddDatasetGetTestCase(
id string,
requestHeaders, responseHeaders http.Header,
response *dataset.Dataset,
) error {
return s.AddTestCase(
http.MethodGet, "/datasets/"+id, http.StatusOK, requestHeaders,
responseHeaders, "", response,
)
}

// AddDatasetCreateTestCase sets up a test case for the api.Client.Datasets.Create() function
func (s *Service) AddDatasetCreateTestCase(
requestHeaders, responseHeaders http.Header,
request, response *dataset.Dataset,
) error {
return s.AddTestCase(
http.MethodPut, "/datasets", http.StatusCreated, requestHeaders,
responseHeaders, request, response,
)
}

// AddDatasetDeleteTestCase sets up a test case for the api.Client.Datasets.Delete() function
func (s *Service) AddDatasetDeleteTestCase(
id string, requestHeaders, responseHeaders http.Header,
) error {
return s.AddTestCase(
http.MethodDelete, "/datasets/"+id, http.StatusNoContent, requestHeaders,
responseHeaders, "", "",
)
}

// AddDatasetGetReportTestCase sets up a test case for the api.Client.Datasets.GetReport() function
func (s *Service) AddDatasetGetReportTestCase(
id string, reportId string, requestHeaders, responseHeaders http.Header, fileContents []byte,
) error {
return s.AddTestCase(
http.MethodGet, "/datasets/"+id+"/reports/"+reportId, http.StatusOK, requestHeaders,
responseHeaders, "", fileContents,
)
}
117 changes: 117 additions & 0 deletions rest/_examples/datasets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package main

import (
"encoding/json"
"fmt"
"gopkg.in/ns1/ns1-go.v2/rest/model/dataset"
"io"
"log"
"net/http"
"os"
"strings"
"time"

api "gopkg.in/ns1/ns1-go.v2/rest"
)

var client *api.Client

// Helper that initializes rest api client from environment variable.
func init() {
k := os.Getenv("NS1_APIKEY")
if k == "" {
fmt.Println("NS1_APIKEY environment variable is not set, giving up")
}

httpClient := &http.Client{Timeout: time.Second * 10}
// Adds logging to each http request.
doer := api.Decorate(httpClient, api.Logging(log.New(os.Stdout, "", log.LstdFlags)))
client = api.NewClient(doer, api.SetAPIKey(k))
}

func main() {
dt, _, err := client.Datasets.Create(&dataset.Dataset{
Name: "My dataset",
Datatype: &dataset.Datatype{
Type: dataset.DatatypeTypeNumQueries,
Scope: dataset.DatatypeScopeAccount,
Data: nil,
},
Repeat: nil,
Timeframe: &dataset.Timeframe{
Aggregation: dataset.TimeframeAggregationMontly,
Cycles: func() *int32 { i := int32(1); return &i }(),
},
ExportType: dataset.ExportTypeCSV,
RecipientEmails: nil,
})
if err != nil {
log.Fatal(err)
}

b, _ := json.MarshalIndent(dt, "", " ")
fmt.Println(string(b))

for {
fmt.Println("waiting for report to be generated...")

dt, _, err = client.Datasets.Get(dt.ID)
if err != nil {
log.Fatal(err)
}
if len(dt.Reports) == 0 {
time.Sleep(time.Second * 5)
continue
}

dtStatus := dt.Reports[0].Status

if dtStatus == dataset.ReportStatusGenerating || dtStatus == dataset.ReportStatusQueued {
time.Sleep(time.Second * 5)
continue
}

if dtStatus == dataset.ReportStatusFailed {
log.Fatal(fmt.Errorf("dataset[%s] report[%s] failed to generate", dt.ID, dt.Reports[0].ID))
}

// report generated
break
}

reportBuf, reportResp, err := client.Datasets.GetReport(dt.ID, dt.Reports[0].ID)
if err != nil {
log.Fatal(err)
}

var fileName string
contentDisposition := reportResp.Header.Get("Content-Disposition")
params := strings.Split(contentDisposition, ";")
for _, param := range params {
param = strings.TrimSpace(param)
if strings.HasPrefix(param, "filename=") {
fileName = strings.TrimPrefix(param, "filename=")
fileName = strings.TrimSpace(fileName)
fileName = strings.Trim(fileName, "\"")
break
}
}

cwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}

file, err := os.Create(fmt.Sprintf("%s/%s", cwd, fileName))
if err != nil {
log.Fatal(err)
}
defer file.Close()

_, err = io.Copy(file, reportBuf)
if err != nil {
log.Fatal(err)
}

fmt.Println("dataset report saved to", fileName)
}
11 changes: 11 additions & 0 deletions rest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
Expand Down Expand Up @@ -88,6 +89,7 @@ type Client struct {
View *DNSViewService
Network *NetworkService
GlobalIPWhitelist *GlobalIPWhitelistService
Datasets *DatasetsService
}

// NewClient constructs and returns a reference to an instantiated Client.
Expand Down Expand Up @@ -134,6 +136,7 @@ func NewClient(httpClient Doer, options ...func(*Client)) *Client {
c.View = (*DNSViewService)(&c.common)
c.Network = (*NetworkService)(&c.common)
c.GlobalIPWhitelist = (*GlobalIPWhitelistService)(&c.common)
c.Datasets = (*DatasetsService)(&c.common)

for _, option := range options {
option(c)
Expand Down Expand Up @@ -199,6 +202,14 @@ func (c Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
}

if v != nil {
// For non-JSON responses, the desired destination might be a bytes buffer
if buf, ok := v.(*bytes.Buffer); ok {
if _, err := io.Copy(buf, resp.Body); err != nil {
return nil, err
}
return resp, err
}

// Try to unmarshal body into given type using streaming decoder.
if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
return nil, err
Expand Down
129 changes: 129 additions & 0 deletions rest/dataset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package rest

import (
"bytes"
"errors"
"fmt"
"net/http"
"strings"

"gopkg.in/ns1/ns1-go.v2/rest/model/dataset"
)

var (
// ErrDatasetNotFound bundles GET/POST/DELETE not found errors.
ErrDatasetNotFound = errors.New("dataset not found")
)

// DatasetsService handles 'datasets' endpoint.
type DatasetsService service

// List returns the configured datasets.
//
// NS1 API docs: https://developer.ibm.com/apis/catalog/ns1--ibm-ns1-connect-api/api/API--ns1--ibm-ns1-connect-api#listDataset
func (s *DatasetsService) List() ([]*dataset.Dataset, *http.Response, error) {
req, err := s.client.NewRequest("GET", "datasets", nil)
if err != nil {
return nil, nil, err
}

dts := make([]*dataset.Dataset, 0)
resp, err := s.client.Do(req, &dts)

return dts, resp, err
}

// Get takes a dataset id and returns all its data.
//
// NS1 API docs: https://developer.ibm.com/apis/catalog/ns1--ibm-ns1-connect-api/api/API--ns1--ibm-ns1-connect-api#getDataset
func (s *DatasetsService) Get(dtID string) (*dataset.Dataset, *http.Response, error) {
path := fmt.Sprintf("datasets/%s", dtID)

req, err := s.client.NewRequest("GET", path, nil)
if err != nil {
return nil, nil, err
}

var dt dataset.Dataset
resp, err := s.client.Do(req, &dt)
if err != nil {
var clientErr *Error
switch {
case errors.As(err, &clientErr):
if strings.HasSuffix(clientErr.Message, " not found") {
return nil, resp, ErrDatasetNotFound
}
}
return nil, resp, err
}

return &dt, resp, nil
}

// Create takes a *Dataset and creates a new dataset.
//
// NS1 API docs: https://developer.ibm.com/apis/catalog/ns1--ibm-ns1-connect-api/api/API--ns1--ibm-ns1-connect-api#createDataset
func (s *DatasetsService) Create(dt *dataset.Dataset) (*dataset.Dataset, *http.Response, error) {
req, err := s.client.NewRequest("PUT", "datasets", dt)
if err != nil {
return nil, nil, err
}

resp, err := s.client.Do(req, dt)
return dt, resp, err
}

// Delete takes a dataset id and deletes it.
//
// NS1 API docs: https://developer.ibm.com/apis/catalog/ns1--ibm-ns1-connect-api/api/API--ns1--ibm-ns1-connect-api#deleteDataset
func (s *DatasetsService) Delete(dtID string) (*http.Response, error) {
path := fmt.Sprintf("datasets/%s", dtID)

req, err := s.client.NewRequest("DELETE", path, nil)
if err != nil {
return nil, err
}

resp, err := s.client.Do(req, nil)
if err != nil {
var clientErr *Error
switch {
case errors.As(err, &clientErr):
if strings.HasSuffix(clientErr.Message, " not found") {
return resp, ErrDatasetNotFound
}
}
return resp, err
}

return resp, nil
}

// GetReport takes a dataset id and a report id and returns bytes.Buffer which contains the file contents
// Additionally, file name can be grabbed from the 'Content-Disposition' header in the http.Response
//
// NS1 API docs: https://developer.ibm.com/apis/catalog/ns1--ibm-ns1-connect-api/api/API--ns1--ibm-ns1-connect-api#getDatasetReport
func (s *DatasetsService) GetReport(dtID string, reportID string) (*bytes.Buffer, *http.Response, error) {
path := fmt.Sprintf("datasets/%s/reports/%s", dtID, reportID)

req, err := s.client.NewRequest("GET", path, nil)
if err != nil {
return nil, nil, err
}

var buf bytes.Buffer

resp, err := s.client.Do(req, &buf)
if err != nil {
var clientErr *Error
switch {
case errors.As(err, &clientErr):
if strings.HasSuffix(clientErr.Message, " not found") {
return nil, resp, ErrDatasetNotFound
}
}
return nil, resp, err
}

return &buf, resp, nil
}
Loading

0 comments on commit b4aa3a8

Please sign in to comment.