netboxgo/netbox.go

333 lines
7.7 KiB
Go
Raw Normal View History

2019-09-11 20:12:42 +02:00
package netboxgo
2019-09-10 22:32:20 +02:00
2019-12-17 16:35:52 +01:00
import (
2021-11-26 11:09:27 +01:00
"bytes"
"context"
2019-12-17 16:35:52 +01:00
"crypto/tls"
"encoding/json"
2021-11-26 11:09:27 +01:00
"fmt"
"io"
2019-12-17 16:35:52 +01:00
"io/ioutil"
2021-11-26 11:09:27 +01:00
"log"
2019-12-17 16:35:52 +01:00
"net/http"
"net/url"
"strings"
"time"
"github.com/pkg/errors"
)
2021-11-26 11:09:27 +01:00
type DCIM struct {
Devices *DevicesService
DeviceRoles *DeviceRolesService
DeviceTypes *DeviceTypesService
Interfaces *InterfacesService
2021-11-26 11:09:27 +01:00
InventoryItems *InventoryItemsService
RearPorts *RearPortsService
}
type Virtualization struct {
Clusters *ClustersService
VirtualMachines *VirtualMachinesService
}
type IPAM struct {
VLANs *VLANsService
VRFs *VRFsService
Prefixes *PrefixesService
}
type Secret struct {
Secrets *SecretsService
}
type Tenancy struct {
Tenants *TenantsService
Sites *SitesService
SiteGroups *SiteGroupsService
}
// Client struct is used to create a new NetBox endpoint
type Client struct {
2021-11-26 13:34:15 +01:00
// DCIM *DCIM
// Tenancy *Tenancy
// IPAM *IPAM
// Virtualization *Virtualization
// Secret *Secret
Tenants *TenantsService
Sites *SitesService
SiteGroups *SiteGroupsService
VirtualMachines *VirtualMachinesService
Clusters *ClustersService
VLANs *VLANsService
Secrets *SecretsService
InventoryItems *InventoryItemsService
Devices *DevicesService
DeviceRoles *DeviceRolesService
DeviceTypes *DeviceTypesService
Interfaces *InterfacesService
Prefixes *PrefixesService
VRFs *VRFsService
RearPorts *RearPortsService
2021-11-26 11:09:27 +01:00
// baseURL is the URL used for the base URL of the API
baseURL *url.URL
// httpClient is used for HTTP requests
httpClient *http.Client
// Reuse a single struct instead of allocating one for each service on the heap.
common service
// UserAgent is used to set the UserAgent on the HTTP requests
UserAgent string
// Token is set for authentication of the API
Token string
// SessionKey is used to read authentication data
SessionKey string
// Used by golang wasm
FetchMode string
// Debug enables verbose debugging messages to console.
Debug bool
// InsecureSkipVerify is used to selectively skip InsecureVerifications
InsecureSkipVerify bool
2021-11-26 11:09:27 +01:00
}
type service struct {
client *Client
2019-09-11 09:19:24 +02:00
}
2020-03-02 10:00:40 +01:00
// NetBoxSessionKey sets the session key for secrets retrieval
2019-12-17 16:35:52 +01:00
type NetBoxSessionKey struct {
XSessionKey string `json:"session_key"`
}
2021-11-26 11:09:27 +01:00
// NewClient returns a new NetBox API client
func NewClient(apiurl string, httpClient *http.Client) (*Client, error) {
c := &Client{}
api, err := url.Parse(apiurl)
if err != nil {
return c, fmt.Errorf("unable to parse url %s: %w", apiurl, err)
}
c.baseURL = api
c.SetDebug(false)
c.SetUserAgent("netboxgo/0.0.6")
c.httpClient = httpClient
if httpClient == nil {
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: false,
CipherSuites: []uint16{
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
2021-11-26 13:39:29 +01:00
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
2021-11-26 11:09:27 +01:00
tls.TLS_CHACHA20_POLY1305_SHA256,
},
MinVersion: tls.VersionTLS12,
},
Proxy: http.ProxyFromEnvironment,
}
timeout := time.Duration(60 * time.Second)
c.httpClient = &http.Client{
Transport: transport,
Jar: nil,
Timeout: timeout,
}
}
c.common.client = c
2021-11-26 13:34:15 +01:00
c.Clusters = (*ClustersService)(&c.common)
c.Devices = (*DevicesService)(&c.common)
c.DeviceRoles = (*DeviceRolesService)(&c.common)
c.DeviceTypes = (*DeviceTypesService)(&c.common)
c.Interfaces = (*InterfacesService)(&c.common)
c.InventoryItems = (*InventoryItemsService)(&c.common)
c.Prefixes = (*PrefixesService)(&c.common)
c.RearPorts = (*RearPortsService)(&c.common)
c.Tenants = (*TenantsService)(&c.common)
c.Secrets = (*SecretsService)(&c.common)
c.Sites = (*SitesService)(&c.common)
c.SiteGroups = (*SiteGroupsService)(&c.common)
c.VirtualMachines = (*VirtualMachinesService)(&c.common)
c.VLANs = (*VLANsService)(&c.common)
c.VRFs = (*VRFsService)(&c.common)
2021-11-26 11:09:27 +01:00
return c, nil
}
// SetUserAgent is used to set the user agent for interaction with the API
func (c *Client) SetUserAgent(u string) {
c.UserAgent = u
}
// SetDebug is used to toggle debugging, set to true for debug
func (c *Client) SetDebug(b bool) {
c.Debug = b
}
// SetToken is used to set the http token bearer for interaction with the API
func (c *Client) SetToken(token string) {
c.Token = token
2019-09-10 22:32:20 +02:00
}
2019-12-17 16:35:52 +01:00
2020-03-02 10:00:40 +01:00
// FetchSessionKey fetches sessionkey
2021-11-26 11:09:27 +01:00
func (n *Client) FetchSessionKey(privatekey string) error {
2019-12-17 16:35:52 +01:00
form := url.Values{}
form.Add("private_key", privatekey)
query := form.Encode()
transport := &http.Transport{
2021-11-26 11:15:19 +01:00
// #nosec XXX: FIXIT
2019-12-17 16:35:52 +01:00
TLSClientConfig: &tls.Config{InsecureSkipVerify: n.InsecureSkipVerify},
}
timeout := time.Duration(60 * time.Second)
client := &http.Client{
Timeout: timeout,
Transport: transport,
}
2021-11-26 11:09:27 +01:00
const secretsPath = "/secrets"
request, err := http.NewRequest("POST", secretsPath+"/get-session-key/", strings.NewReader(query))
2019-12-17 16:35:52 +01:00
if err != nil {
return err
}
2021-11-26 11:09:27 +01:00
2019-12-17 16:35:52 +01:00
request.Header.Add("Accept", "application/json")
request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
2020-09-03 13:52:52 +02:00
if n.FetchMode != "" {
request.Header.Add("js.fetch:mode", "no-cors")
}
2019-12-17 16:35:52 +01:00
request.Header.Add("Authorization", " Token "+n.Token)
response, err := client.Do(request)
if err != nil {
return err
}
if response.StatusCode != http.StatusOK {
2021-11-26 11:09:27 +01:00
return errors.Errorf("response was: %d should be %d\n%s\n", response.StatusCode, http.StatusOK, response.Header)
2019-12-17 16:35:52 +01:00
}
data, err := ioutil.ReadAll(response.Body)
if err != nil {
return err
}
err = response.Body.Close()
if err != nil {
return err
}
var sessionkey NetBoxSessionKey
err = json.Unmarshal(data, &sessionkey)
if err != nil {
return err
}
n.SessionKey = sessionkey.XSessionKey
return nil
}
2021-11-26 11:09:27 +01:00
// newRequest is used to make new request to endpoints
func (c *Client) newRequest(ctx context.Context, method, path string, query string, body interface{}) (*http.Request, error) {
var err error
var buf io.ReadWriter
u := &url.URL{
Scheme: c.baseURL.Scheme,
Host: c.baseURL.Host,
Path: c.baseURL.Path + path,
RawQuery: query,
}
if body != nil {
buf = new(bytes.Buffer)
err = json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, err
}
}
readBuf, _ := json.Marshal(body)
c.debugf("debug: method:%s url:%s body:%s", method, u.String(), string(readBuf))
req, err := http.NewRequestWithContext(ctx, method, u.String(), buf)
if err != nil {
return nil, err
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
if c.SessionKey != "" {
req.Header.Add("X-Session-Key", c.SessionKey)
}
req.Header.Set("Accept", "application/json")
if c.UserAgent != "" {
req.Header.Set("User-Agent", c.UserAgent)
}
if c.Token != "" {
req.Header.Add("Authorization", "Bearer "+c.Token)
}
if c.SessionKey != "" {
req.Header.Add("X-Session-Key", c.SessionKey)
}
return req, nil
}
// do method is used to decode request response to proper struct
func (c *Client) do(req *http.Request, v interface{}) (*http.Response, error) {
var err error
var resp *http.Response
resp, err = c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("client unable to do request: %w", err)
}
if resp.StatusCode < 200 || resp.StatusCode > 299 {
var data []byte
data, err = io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("unable to read body response: %w", err)
}
return nil, fmt.Errorf("request failed: body=%s", string(data))
}
if v != nil {
err = json.NewDecoder(resp.Body).Decode(v)
if err != nil {
return resp, fmt.Errorf("unable to decode response body: %w", err)
}
}
return resp, nil
}
// debugf is used as a formtter for debugging information
func (c *Client) debugf(fmt string, v ...interface{}) {
if c.Debug {
log.Printf("cendot client: "+fmt, v...)
}
}