// Package netboxapi provides custom functions for the default // go-netbox package. It mostly handles pagination. package netboxapi import ( "bytes" "encoding/json" "fmt" "io" "net/http" e "git.stinnesbeck.com/nils/errorhandler" transport "github.com/go-openapi/runtime/client" "github.com/netbox-community/go-netbox/v3/netbox/client" ) // Client is the type we use to add custom functions type Client client.NetBoxAPI // NetBoxAPI is the struct we use to glue everything together type NetBoxAPI struct { api Client Token string URL string } type apiResponse struct { Count int64 `json:"count"` Next string `json:"next"` Previous string `json:"previous"` Results []any `json:"results"` } // NewNetBoxClient returns a client to NetBox at the given url, // using the provided token func NewNetBoxClient(token string, url string) (NetBoxAPI, error) { // Construct the API basis and configure authentication t := transport.New(url, client.DefaultBasePath, []string{"https"}) t.DefaultAuthentication = transport.APIKeyAuth( "Authorization", "header", fmt.Sprintf("Token %v", token), ) // create NetBox api variable n := client.New(t, nil) // create a new variable for our custom functions api := Client(*n) nb := NetBoxAPI{ api: api, Token: token, URL: url, } return nb, nil } func (nb NetBoxAPI) handleRawPagination(url string) (*apiResponse, error) { var response apiResponse b, err := nb.rawNetBoxAPICall(url, http.MethodGet) if err != nil { return nil, e.FormatError("can't perform raw NetBox API call", err) } // Unmarshal json into response if err := json.Unmarshal(b, &response); err != nil { return nil, e.FormatError("can't unmarshal json payload in raw NetBox API call", err) } // there are no more pages left, return the response we got if response.Next == "" { return &response, nil } // there are more pages left, get more responses from there // recursive calling of handlePagination to get all following pages nextResponse, err := nb.handleRawPagination(response.Next) if err != nil { return nil, e.FormatError("can't get next response from api", err) } // append all next responses' results to this response's result response.Results = append(response.Results, nextResponse.Results...) // go back up the chain through recursion return &response, nil } func (nb NetBoxAPI) handlePagination(url string, payload any) error { response, err := nb.handleRawPagination(url) if err != nil { return e.FormatError("can't handle pagination for url "+url, err) } fmt.Println(response.Count) // cast response into our payload if err := resultToOtherStructure(*response, &payload); err != nil { return e.FormatError("can't parse result to other Structure", err) } // prettify.Print(payload) return nil } func resultToOtherStructure(input apiResponse, output any) error { // get JSON representation of results c, err := json.Marshal(input.Results) if err != nil { return err } // Unmarshal onto specific structure via output return json.Unmarshal(c, &output) } func (nb NetBoxAPI) rawNetBoxAPICall(url string, method string, data ...any) ([]byte, error) { // construct complete URL (url already comes with a "/" after the "/api" part) // url = fmt.Sprintf("https://%s/api%s", nb.URL, url) // log.Printf("calling API on url: %s with method %s and data %+v\n", url, method, data) // create new transport client c := &http.Client{} var req *http.Request switch method { case http.MethodPatch, http.MethodPost, http.MethodPut: // to patch something we need data, check it exists if data == nil { return nil, fmt.Errorf("no data was provided, please provide data to use PATCH") } payload, err := json.Marshal(data) if err != nil { return nil, e.FormatError(fmt.Sprintf("can't marshal data %+v for apicall %s", data, url), err) } // construct request innerRequest, err := http.NewRequest(method, url, bytes.NewBuffer(payload)) if err != nil { return nil, e.FormatError(fmt.Sprintf("can't construct new request (method: %s, URL: %s, payload %s)", method, url, payload), err) } // hand over request to outer request req = innerRequest case http.MethodGet: innerRequest, err := http.NewRequest(method, url, nil) if err != nil { return nil, e.FormatError(fmt.Sprintf("can't construct new request (method: %s, URL: %s)", method, url), err) } // hand over request to outer request req = innerRequest } // set request headers req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Token %s", nb.Token)) // call API client with request resp, err := c.Do(req) if err != nil { return nil, e.FormatError("can't get response from "+url, err) } defer resp.Body.Close() // read body of response body, err := io.ReadAll(resp.Body) if err != nil { return nil, e.FormatError("can't read response body", err) } switch resp.StatusCode { case 200, 201: return body, nil default: return body, fmt.Errorf("got bad StatusCode %d in response from API, data is:\n\t-> %+v", resp.StatusCode, data) } }