1package rest 2 3import ( 4 "errors" 5 "fmt" 6 "net/http" 7 8 "gopkg.in/ns1/ns1-go.v2/rest/model/ipam" 9) 10 11// IPAMService handles the 'ipam' endpoint. 12type IPAMService service 13 14// ListAddrs returns a list of all root addresses (i.e. Parent == 0) in all 15// networks. 16// 17// NS1 API docs: https://ns1.com/api#getview-a-list-of-root-addresses 18func (s *IPAMService) ListAddrs() ([]ipam.Address, *http.Response, error) { 19 req, err := s.client.NewRequest(http.MethodGet, "ipam/address", nil) 20 if err != nil { 21 return nil, nil, err 22 } 23 24 addrs := []ipam.Address{} 25 var resp *http.Response 26 if s.client.FollowPagination { 27 resp, err = s.client.DoWithPagination(req, &addrs, s.nextAddrs) 28 } else { 29 resp, err = s.client.Do(req, &addrs) 30 } 31 if err != nil { 32 return nil, resp, err 33 } 34 35 return addrs, resp, nil 36} 37 38// GetSubnet returns the subnet corresponding to the provided address ID. 39// 40// NS1 API docs: https://ns1.com/api#getview-a-subnet 41func (s *IPAMService) GetSubnet(addrID int) (*ipam.Address, *http.Response, error) { 42 reqPath := fmt.Sprintf("ipam/address/%d", addrID) 43 req, err := s.client.NewRequest(http.MethodGet, reqPath, nil) 44 if err != nil { 45 return nil, nil, err 46 } 47 48 addr := &ipam.Address{} 49 var resp *http.Response 50 resp, err = s.client.Do(req, addr) 51 if err != nil { 52 return nil, resp, err 53 } 54 55 return addr, resp, nil 56} 57 58// GetChildren requests a list of all child addresses (or subnets) for the 59// specified IP address. 60// 61// NS1 API docs: https://ns1.com/api#getview-address-children 62func (s *IPAMService) GetChildren(addrID int) ([]*ipam.Address, *http.Response, error) { 63 reqPath := fmt.Sprintf("ipam/address/%d/children", addrID) 64 req, err := s.client.NewRequest(http.MethodGet, reqPath, nil) 65 if err != nil { 66 return nil, nil, err 67 } 68 69 addrs := []*ipam.Address{} 70 var resp *http.Response 71 if s.client.FollowPagination { 72 resp, err = s.client.DoWithPagination(req, &addrs, s.nextAddrs) 73 } else { 74 resp, err = s.client.Do(req, &addrs) 75 } 76 if err != nil { 77 return nil, resp, err 78 } 79 80 return addrs, resp, nil 81} 82 83// GetParent fetches the addresses parent. 84// 85// NS1 API docs: https://ns1.com/api#getview-address-parent 86func (s *IPAMService) GetParent(addrID int) (*ipam.Address, *http.Response, error) { 87 reqPath := fmt.Sprintf("ipam/address/%d/parent", addrID) 88 req, err := s.client.NewRequest(http.MethodGet, reqPath, nil) 89 if err != nil { 90 return nil, nil, err 91 } 92 93 addr := &ipam.Address{} 94 var resp *http.Response 95 resp, err = s.client.Do(req, addr) 96 if err != nil { 97 return nil, resp, err 98 } 99 100 return addr, resp, nil 101} 102 103// CreateSubnet creates an address or subnet. 104// The Prefix and Network fields are required. 105// 106// NS1 API docs: https://ns1.com/api#putcreate-a-subnet 107func (s *IPAMService) CreateSubnet(addr *ipam.Address) (*ipam.Address, *http.Response, error) { 108 switch { 109 case addr.Prefix == "": 110 return nil, nil, errors.New("the Prefix field is required") 111 case addr.Network == 0: 112 return nil, nil, errors.New("the Network field is required") 113 } 114 115 req, err := s.client.NewRequest(http.MethodPut, "ipam/address", addr) 116 if err != nil { 117 return nil, nil, err 118 } 119 120 respAddr := &ipam.Address{} 121 var resp *http.Response 122 resp, err = s.client.Do(req, respAddr) 123 if err != nil { 124 return nil, resp, err 125 } 126 127 return respAddr, resp, nil 128} 129 130// EditSubnet updates an existing subnet. 131// The ID field is required. 132// Parent is whether or not to include the parent in the parent field. 133// 134// NS1 API docs: https://ns1.com/api#postedit-a-subnet 135func (s *IPAMService) EditSubnet(addr *ipam.Address, parent bool) (newAddr, parentAddr *ipam.Address, resp *http.Response, err error) { 136 if addr.ID == 0 { 137 return nil, nil, nil, errors.New("the ID field is required") 138 } 139 140 reqPath := fmt.Sprintf("ipam/address/%d", addr.ID) 141 req, err := s.client.NewRequest(http.MethodPost, reqPath, addr) 142 if err != nil { 143 return nil, nil, nil, err 144 } 145 if parent { 146 q := req.URL.Query() 147 q.Add("parent", "true") 148 req.URL.RawQuery = q.Encode() 149 } 150 151 data := struct { 152 ipam.Address 153 Parent ipam.Address `json:"parent"` 154 }{} 155 resp, err = s.client.Do(req, &data) 156 if err != nil { 157 return nil, nil, resp, err 158 } 159 160 if parent { 161 return &data.Address, &data.Parent, resp, nil 162 } 163 return &data.Address, nil, resp, nil 164} 165 166// SplitSubnet splits a block of unassigned IP space into equal pieces. 167// This will not function with ranges or individual hosts. Normal breaking out 168// of a subnet is done with the standard PUT route. (Eg. root address is a /24 169// and request for /29s will break it into 32 /29s) 170// 171// - Only planned subnets can be split 172// - Name and description will be unset on children 173// - KVPS and options will be copied; tags will be inherited 174// 175// NS1 API docs: https://ns1.com/api#postsplit-a-subnet 176func (s *IPAMService) SplitSubnet(id, prefix int) (rootAddr int, prefixIDs []int, resp *http.Response, err error) { 177 reqPath := fmt.Sprintf("ipam/address/%d/split", id) 178 req, err := s.client.NewRequest(http.MethodPost, reqPath, struct { 179 Prefix int `json:"prefix"` 180 }{ 181 Prefix: prefix, 182 }) 183 if err != nil { 184 return 0, nil, nil, err 185 } 186 187 data := &struct { 188 RootAddr int `json:"root_address_id"` 189 PrefixIDs []int `json:"prefix_ids"` 190 }{} 191 resp, err = s.client.Do(req, &data) 192 return data.RootAddr, data.PrefixIDs, resp, err 193} 194 195// MergeSubnet merges several subnets together. 196// 197// NS1 API docs: https://ns1.com/api#postmerge-a-subnet 198func (s *IPAMService) MergeSubnet(rootID, mergeID int) (*ipam.Address, *http.Response, error) { 199 req, err := s.client.NewRequest(http.MethodPost, "ipam/address/merge", struct { 200 Root int `json:"root_address_id"` 201 Merge int `json:"merged_address_id"` 202 }{ 203 Root: rootID, 204 Merge: mergeID, 205 }) 206 if err != nil { 207 return nil, nil, err 208 } 209 210 addr := &ipam.Address{} 211 resp, err := s.client.Do(req, &addr) 212 return addr, resp, err 213} 214 215// DeleteSubnet removes a subnet entirely. 216// 217// NS1 API docs: https://ns1.com/api#deletedelete-a-subnet 218func (s *IPAMService) DeleteSubnet(id int) (*http.Response, error) { 219 reqPath := fmt.Sprintf("ipam/address/%d", id) 220 req, err := s.client.NewRequest(http.MethodDelete, reqPath, nil) 221 if err != nil { 222 return nil, err 223 } 224 225 return s.client.Do(req, nil) 226} 227 228// nextAddrs is a pagination helper than gets and appends another list of 229// addresses to the passed list. 230func (s *IPAMService) nextAddrs(v *interface{}, uri string) (*http.Response, error) { 231 addrs := []*ipam.Address{} 232 resp, err := s.client.getURI(&addrs, uri) 233 if err != nil { 234 return resp, err 235 } 236 addrList, ok := (*v).(*[]*ipam.Address) 237 if !ok { 238 return nil, fmt.Errorf( 239 "incorrect value for v, expected value of type *[]ipam.Address, got: %T", v, 240 ) 241 } 242 *addrList = append(*addrList, addrs...) 243 return resp, nil 244} 245