Skip to content

Commit 0859a2b

Browse files
authored
Merge pull request #63 from OpenVPN/feature/support-1.1.0-api
Feature/support 1.1.0 api
2 parents 9851d3b + 40880fd commit 0859a2b

14 files changed

+1977
-20
lines changed

README.md

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
Official Go client library for the Cloud Connexa API, providing programmatic access to OpenVPN Cloud Connexa services.
99

10+
**Full CloudConnexa API v1.1.0 Support** - Complete coverage of all public API endpoints with modern Go patterns.
11+
1012
## Table of Contents
1113

1214
- [Installation](#installation)
@@ -201,23 +203,51 @@ if err != nil {
201203

202204
## API Coverage
203205

204-
The client provides comprehensive coverage of the Cloud Connexa API:
206+
The client provides **100% coverage** of the CloudConnexa API v1.1.0 with all public endpoints:
207+
208+
### **Core Resources**
209+
210+
- **Networks** - Complete network lifecycle management (CRUD operations)
211+
- **Users** - User management, authentication, and device associations
212+
- **User Groups** - Group policies, permissions, and access control
213+
- **VPN Regions** - Available VPN server regions and capabilities
214+
215+
### **Connectivity & Infrastructure**
216+
217+
- **Network Connectors** - Site-to-site connectivity with IPsec tunnel support
218+
- **Host Connectors** - Host-based connectivity and routing
219+
- **Hosts** - Host configuration, monitoring, and IP services
220+
- **Routes** - Network routing configuration and management
221+
222+
### **Services & Monitoring**
223+
224+
- **DNS Records** - Private DNS management with direct endpoint access
225+
- **Host IP Services** - Service definitions and port configurations
226+
- **Sessions** - OpenVPN session monitoring and analytics
227+
- **Devices** - Device lifecycle management and security controls
228+
229+
### **Security & Access Control**
230+
231+
- **Access Groups** - Fine-grained access policies and rules
232+
- **Location Contexts** - Location-based access controls
233+
- **Settings** - System-wide configuration and preferences
234+
235+
### **API v1.1.0 Features**
205236

206-
- **Networks**: Create, read, update, delete networks
207-
- **Users**: User lifecycle management and authentication
208-
- **Connectors**: Connector deployment and management
209-
- **Hosts**: Host configuration and monitoring
210-
- **DNS Records**: DNS management for private networks
211-
- **Routes**: Network routing configuration
212-
- **VPN Regions**: Available VPN server regions
213-
- **Access Groups**: User access control and policies
237+
- **Direct Endpoints**: Optimized single-call access for DNS Records and User Groups
238+
- **Enhanced Sessions API**: Complete OpenVPN session monitoring with cursor-based pagination
239+
- **Comprehensive Devices API**: Full device management with filtering and bulk operations
240+
- **IPsec Support**: Start/stop IPsec tunnels for Network Connectors
241+
- **Updated DTOs**: Simplified data structures aligned with API v1.1.0
214242

215-
All endpoints support:
243+
### **All Endpoints Support**
216244

217-
- Pagination for list operations
218-
- Error handling with detailed error types
219-
- Automatic rate limiting
220-
- Concurrent-safe operations
245+
- **Pagination** - Both cursor-based (Sessions) and page-based (legacy) pagination
246+
- **Error Handling** - Structured error types with detailed messages
247+
- **Rate Limiting** - Automatic rate limiting with configurable limits
248+
- **Type Safety** - Strong typing with comprehensive validation
249+
- **Concurrent Safety** - Thread-safe operations for production use
250+
- **Performance Optimized** - Direct API calls where available
221251

222252
## Configuration
223253

cloudconnexa/cloudconnexa.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ type Client struct {
4545
LocationContexts *LocationContextsService
4646
AccessGroups *AccessGroupsService
4747
Settings *SettingsService
48+
Sessions *SessionsService
49+
Devices *DevicesService
4850
}
4951

5052
type service struct {
@@ -136,6 +138,8 @@ func NewClient(baseURL, clientID, clientSecret string) (*Client, error) {
136138
c.LocationContexts = (*LocationContextsService)(&c.common)
137139
c.AccessGroups = (*AccessGroupsService)(&c.common)
138140
c.Settings = (*SettingsService)(&c.common)
141+
c.Sessions = (*SessionsService)(&c.common)
142+
c.Devices = (*DevicesService)(&c.common)
139143
return c, nil
140144
}
141145

cloudconnexa/devices.go

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
package cloudconnexa
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"net/url"
9+
"strconv"
10+
"time"
11+
)
12+
13+
// DeviceStatus represents the possible statuses of a device.
14+
type DeviceStatus string
15+
16+
const (
17+
// DeviceStatusActive represents an active device.
18+
DeviceStatusActive DeviceStatus = "ACTIVE"
19+
// DeviceStatusInactive represents an inactive device.
20+
DeviceStatusInactive DeviceStatus = "INACTIVE"
21+
// DeviceStatusBlocked represents a blocked device.
22+
DeviceStatusBlocked DeviceStatus = "BLOCKED"
23+
// DeviceStatusPending represents a pending device.
24+
DeviceStatusPending DeviceStatus = "PENDING"
25+
)
26+
27+
// DeviceType represents the type of device.
28+
type DeviceType string
29+
30+
const (
31+
// DeviceTypeClient represents a client device.
32+
DeviceTypeClient DeviceType = "CLIENT"
33+
// DeviceTypeConnector represents a connector device.
34+
DeviceTypeConnector DeviceType = "CONNECTOR"
35+
)
36+
37+
// DeviceDetail represents a detailed device in CloudConnexa.
38+
type DeviceDetail struct {
39+
ID string `json:"id"`
40+
Name string `json:"name"`
41+
Description string `json:"description,omitempty"`
42+
UserID string `json:"userId"`
43+
Status string `json:"status"`
44+
Type string `json:"type"`
45+
Platform string `json:"platform,omitempty"`
46+
Version string `json:"version,omitempty"`
47+
LastSeen *time.Time `json:"lastSeen,omitempty"`
48+
CreatedAt time.Time `json:"createdAt"`
49+
UpdatedAt time.Time `json:"updatedAt"`
50+
IPv4Address string `json:"ipv4Address,omitempty"`
51+
IPv6Address string `json:"ipv6Address,omitempty"`
52+
PublicKey string `json:"publicKey,omitempty"`
53+
Fingerprint string `json:"fingerprint,omitempty"`
54+
SerialNumber string `json:"serialNumber,omitempty"`
55+
MACAddress string `json:"macAddress,omitempty"`
56+
Hostname string `json:"hostname,omitempty"`
57+
OperatingSystem string `json:"operatingSystem,omitempty"`
58+
OSVersion string `json:"osVersion,omitempty"`
59+
ClientVersion string `json:"clientVersion,omitempty"`
60+
IsOnline bool `json:"isOnline"`
61+
LastConnectedAt *time.Time `json:"lastConnectedAt,omitempty"`
62+
LastDisconnectedAt *time.Time `json:"lastDisconnectedAt,omitempty"`
63+
TotalBytesIn int64 `json:"totalBytesIn"`
64+
TotalBytesOut int64 `json:"totalBytesOut"`
65+
SessionCount int `json:"sessionCount"`
66+
Region string `json:"region,omitempty"`
67+
Gateway string `json:"gateway,omitempty"`
68+
UserGroupID string `json:"userGroupId,omitempty"`
69+
NetworkID string `json:"networkId,omitempty"`
70+
Tags []string `json:"tags,omitempty"`
71+
Metadata map[string]interface{} `json:"metadata,omitempty"`
72+
}
73+
74+
// DevicePageResponse represents a paginated response of devices.
75+
type DevicePageResponse struct {
76+
Content []DeviceDetail `json:"content"`
77+
NumberOfElements int `json:"numberOfElements"`
78+
Page int `json:"page"`
79+
Size int `json:"size"`
80+
Success bool `json:"success"`
81+
TotalElements int `json:"totalElements"`
82+
TotalPages int `json:"totalPages"`
83+
}
84+
85+
// DeviceListOptions represents the options for listing devices.
86+
type DeviceListOptions struct {
87+
UserID string `json:"userId,omitempty"`
88+
Page int `json:"page,omitempty"`
89+
Size int `json:"size,omitempty"`
90+
}
91+
92+
// DeviceUpdateRequest represents the request body for updating a device.
93+
type DeviceUpdateRequest struct {
94+
Name string `json:"name,omitempty"`
95+
Description string `json:"description,omitempty"`
96+
Status string `json:"status,omitempty"`
97+
Tags []string `json:"tags,omitempty"`
98+
Metadata map[string]interface{} `json:"metadata,omitempty"`
99+
}
100+
101+
// DevicesService provides methods for managing devices.
102+
type DevicesService service
103+
104+
// List retrieves a list of devices with optional filtering and pagination.
105+
func (d *DevicesService) List(options DeviceListOptions) (*DevicePageResponse, error) {
106+
// Build query parameters
107+
params := url.Values{}
108+
109+
if options.UserID != "" {
110+
params.Set("userId", options.UserID)
111+
}
112+
113+
if options.Page > 0 {
114+
params.Set("page", strconv.Itoa(options.Page))
115+
}
116+
117+
if options.Size > 0 {
118+
// Validate size parameter (1-1000 according to API docs)
119+
if options.Size < 1 || options.Size > 1000 {
120+
return nil, fmt.Errorf("size must be between 1 and 1000, got %d", options.Size)
121+
}
122+
params.Set("size", strconv.Itoa(options.Size))
123+
}
124+
125+
endpoint := fmt.Sprintf("%s/devices", d.client.GetV1Url())
126+
if len(params) > 0 {
127+
endpoint += "?" + params.Encode()
128+
}
129+
130+
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
131+
if err != nil {
132+
return nil, err
133+
}
134+
135+
body, err := d.client.DoRequest(req)
136+
if err != nil {
137+
return nil, err
138+
}
139+
140+
var response DevicePageResponse
141+
err = json.Unmarshal(body, &response)
142+
if err != nil {
143+
return nil, err
144+
}
145+
146+
return &response, nil
147+
}
148+
149+
// GetByPage retrieves devices using pagination.
150+
func (d *DevicesService) GetByPage(page int, pageSize int) (*DevicePageResponse, error) {
151+
options := DeviceListOptions{
152+
Page: page,
153+
Size: pageSize,
154+
}
155+
return d.List(options)
156+
}
157+
158+
// ListAll retrieves all devices by paginating through all available pages.
159+
func (d *DevicesService) ListAll() ([]DeviceDetail, error) {
160+
var allDevices []DeviceDetail
161+
page := 0
162+
pageSize := 100 // Use maximum page size for efficiency
163+
164+
for {
165+
response, err := d.GetByPage(page, pageSize)
166+
if err != nil {
167+
return nil, err
168+
}
169+
170+
allDevices = append(allDevices, response.Content...)
171+
172+
// If we've reached the last page, break
173+
if page >= response.TotalPages-1 {
174+
break
175+
}
176+
page++
177+
}
178+
179+
return allDevices, nil
180+
}
181+
182+
// GetByID retrieves a specific device by its ID.
183+
func (d *DevicesService) GetByID(deviceID string) (*DeviceDetail, error) {
184+
endpoint := fmt.Sprintf("%s/devices/%s", d.client.GetV1Url(), deviceID)
185+
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
186+
if err != nil {
187+
return nil, err
188+
}
189+
190+
body, err := d.client.DoRequest(req)
191+
if err != nil {
192+
return nil, err
193+
}
194+
195+
var device DeviceDetail
196+
err = json.Unmarshal(body, &device)
197+
if err != nil {
198+
return nil, err
199+
}
200+
201+
return &device, nil
202+
}
203+
204+
// Update updates an existing device by its ID.
205+
func (d *DevicesService) Update(deviceID string, updateRequest DeviceUpdateRequest) (*DeviceDetail, error) {
206+
requestJSON, err := json.Marshal(updateRequest)
207+
if err != nil {
208+
return nil, err
209+
}
210+
211+
endpoint := fmt.Sprintf("%s/devices/%s", d.client.GetV1Url(), deviceID)
212+
req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(requestJSON))
213+
if err != nil {
214+
return nil, err
215+
}
216+
217+
body, err := d.client.DoRequest(req)
218+
if err != nil {
219+
return nil, err
220+
}
221+
222+
var device DeviceDetail
223+
err = json.Unmarshal(body, &device)
224+
if err != nil {
225+
return nil, err
226+
}
227+
228+
return &device, nil
229+
}
230+
231+
// ListByUserID retrieves all devices for a specific user.
232+
func (d *DevicesService) ListByUserID(userID string) ([]DeviceDetail, error) {
233+
var allDevices []DeviceDetail
234+
page := 0
235+
pageSize := 100
236+
237+
for {
238+
options := DeviceListOptions{
239+
UserID: userID,
240+
Page: page,
241+
Size: pageSize,
242+
}
243+
244+
response, err := d.List(options)
245+
if err != nil {
246+
return nil, err
247+
}
248+
249+
allDevices = append(allDevices, response.Content...)
250+
251+
// If we've reached the last page, break
252+
if page >= response.TotalPages-1 {
253+
break
254+
}
255+
page++
256+
}
257+
258+
return allDevices, nil
259+
}
260+
261+
// Block blocks a device by updating its status to BLOCKED.
262+
func (d *DevicesService) Block(deviceID string) (*DeviceDetail, error) {
263+
updateRequest := DeviceUpdateRequest{
264+
Status: string(DeviceStatusBlocked),
265+
}
266+
return d.Update(deviceID, updateRequest)
267+
}
268+
269+
// Unblock unblocks a device by updating its status to ACTIVE.
270+
func (d *DevicesService) Unblock(deviceID string) (*DeviceDetail, error) {
271+
updateRequest := DeviceUpdateRequest{
272+
Status: string(DeviceStatusActive),
273+
}
274+
return d.Update(deviceID, updateRequest)
275+
}
276+
277+
// UpdateName updates the name of a device.
278+
func (d *DevicesService) UpdateName(deviceID string, name string) (*DeviceDetail, error) {
279+
updateRequest := DeviceUpdateRequest{
280+
Name: name,
281+
}
282+
return d.Update(deviceID, updateRequest)
283+
}
284+
285+
// UpdateDescription updates the description of a device.
286+
func (d *DevicesService) UpdateDescription(deviceID string, description string) (*DeviceDetail, error) {
287+
updateRequest := DeviceUpdateRequest{
288+
Description: description,
289+
}
290+
return d.Update(deviceID, updateRequest)
291+
}
292+
293+
// UpdateTags updates the tags of a device.
294+
func (d *DevicesService) UpdateTags(deviceID string, tags []string) (*DeviceDetail, error) {
295+
updateRequest := DeviceUpdateRequest{
296+
Tags: tags,
297+
}
298+
return d.Update(deviceID, updateRequest)
299+
}

0 commit comments

Comments
 (0)