Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ jobs:
strategy:
matrix:
go:
- "1.21"
- "1.22"
- "1.23"
- "1.24"

steps:
- uses: actions/checkout@v4
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ jobs:
go-version: "1.23"

- name: golangci-lint
uses: golangci/golangci-lint-action@v6
uses: golangci/golangci-lint-action@v8
with:
version: latest
version: v2.1
args: --timeout=5m

- name: Check formatting
Expand Down
33 changes: 17 additions & 16 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
version: "2"

run:
timeout: 5m
tests: true
go: "1.23"

linters:
enable:
- gofmt
- govet
- revive
- gosimple
- staticcheck
- errcheck
- ineffassign
- gosec
- govet
- ineffassign
- misspell
- revive
- staticcheck
- unconvert

run:
timeout: 5m
tests: true

issues:
exclude-rules:
- path: _test\.go
linters:
- gosec
- unused
exclusions:
rules:
- path: _test\.go
linters:
- gosec
21 changes: 21 additions & 0 deletions cloudconnexa/access_groups.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Package cloudconnexa provides a Go client library for the CloudConnexa API.
// It offers comprehensive functionality for managing VPN networks, hosts, connectors,
// routes, users, and other CloudConnexa resources through a simple Go interface.
package cloudconnexa

import (
Expand All @@ -7,6 +10,8 @@ import (
"net/http"
)

// AccessGroup represents a group of access rules that define network access permissions.
// It contains source and destination rules that determine what resources can access each other.
type AccessGroup struct {
ID string `json:"id"`
Name string `json:"name"`
Expand All @@ -15,17 +20,22 @@ type AccessGroup struct {
Destination []AccessItem `json:"destination"`
}

// AccessItem represents a single access rule item that can be either a source or destination.
// It defines what resources are covered by the access rule and their relationships.
type AccessItem struct {
Type string `json:"type"`
AllCovered bool `json:"allCovered"`
Parent string `json:"parent,omitempty"`
Children []string `json:"children,omitempty"`
}

// Item represents a basic resource with an identifier.
type Item struct {
ID string `json:"id"`
}

// AccessGroupPageResponse represents a paginated response from the CloudConnexa API
// containing a list of access groups and pagination metadata.
type AccessGroupPageResponse struct {
Content []AccessGroup `json:"content"`
NumberOfElements int `json:"numberOfElements"`
Expand All @@ -36,8 +46,11 @@ type AccessGroupPageResponse struct {
TotalPages int `json:"totalPages"`
}

// AccessGroupsService handles communication with the CloudConnexa API for access group operations.
type AccessGroupsService service

// GetAccessGroupsByPage retrieves a paginated list of access groups from the CloudConnexa API.
// It returns the access groups for the specified page and page size.
func (c *AccessGroupsService) GetAccessGroupsByPage(page int, size int) (AccessGroupPageResponse, error) {
endpoint := fmt.Sprintf("%s/access-groups?page=%d&size=%d", c.client.GetV1Url(), page, size)
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
Expand All @@ -58,6 +71,8 @@ func (c *AccessGroupsService) GetAccessGroupsByPage(page int, size int) (AccessG
return response, nil
}

// List retrieves all access groups from the CloudConnexa API.
// It handles pagination internally and returns a complete list of access groups.
func (c *AccessGroupsService) List() ([]AccessGroup, error) {
var allGroups []AccessGroup
page := 0
Expand All @@ -78,6 +93,7 @@ func (c *AccessGroupsService) List() ([]AccessGroup, error) {
return allGroups, nil
}

// Get retrieves a specific access group by its ID from the CloudConnexa API.
func (c *AccessGroupsService) Get(id string) (*AccessGroup, error) {
endpoint := fmt.Sprintf("%s/access-groups/%s", c.client.GetV1Url(), id)
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
Expand All @@ -98,6 +114,8 @@ func (c *AccessGroupsService) Get(id string) (*AccessGroup, error) {
return &accessGroup, nil
}

// Create creates a new access group in the CloudConnexa API.
// It returns the created access group with its assigned ID.
func (c *AccessGroupsService) Create(accessGroup *AccessGroup) (*AccessGroup, error) {
accessGroupJSON, err := json.Marshal(accessGroup)
if err != nil {
Expand All @@ -124,6 +142,8 @@ func (c *AccessGroupsService) Create(accessGroup *AccessGroup) (*AccessGroup, er
return &s, nil
}

// Update updates an existing access group in the CloudConnexa API.
// It returns the updated access group.
func (c *AccessGroupsService) Update(id string, accessGroup *AccessGroup) (*AccessGroup, error) {
accessGroupJSON, err := json.Marshal(accessGroup)
if err != nil {
Expand All @@ -150,6 +170,7 @@ func (c *AccessGroupsService) Update(id string, accessGroup *AccessGroup) (*Acce
return &s, nil
}

// Delete removes an access group from the CloudConnexa API by its ID.
func (c *AccessGroupsService) Delete(id string) error {
endpoint := fmt.Sprintf("%s/access-groups/%s", c.client.GetV1Url(), id)
req, err := http.NewRequest(http.MethodDelete, endpoint, nil)
Expand Down
23 changes: 21 additions & 2 deletions cloudconnexa/cloudconnexa.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
userAgent = "cloudconnexa-go"
)

// Client represents a CloudConnexa API client with all service endpoints.
type Client struct {
client *http.Client

Expand Down Expand Up @@ -49,10 +50,12 @@ type service struct {
client *Client
}

// Credentials represents the OAuth2 token response from CloudConnexa API.
type Credentials struct {
AccessToken string `json:"access_token"`
}

// ErrClientResponse represents an error response from the CloudConnexa API.
type ErrClientResponse struct {
status int
body string
Expand All @@ -62,6 +65,8 @@ func (e ErrClientResponse) Error() string {
return fmt.Sprintf("status code: %d, response body: %s", e.status, e.body)
}

// NewClient creates a new CloudConnexa API client with the given credentials.
// It authenticates using OAuth2 client credentials flow and returns a configured client.
func NewClient(baseURL, clientID, clientSecret string) (*Client, error) {
if clientID == "" || clientSecret == "" {
return nil, ErrCredentialsRequired
Expand All @@ -88,7 +93,13 @@ func NewClient(baseURL, clientID, clientSecret string) (*Client, error) {
return nil, err
}

defer resp.Body.Close()
defer func() {
if closeErr := resp.Body.Close(); closeErr != nil {
// Log the error if you have a logger, otherwise this is acceptable for library code
_ = closeErr
}
}()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
Expand Down Expand Up @@ -126,6 +137,8 @@ func NewClient(baseURL, clientID, clientSecret string) (*Client, error) {
return c, nil
}

// DoRequest executes an HTTP request with authentication and rate limiting.
// It automatically adds the Bearer token, sets headers, and handles errors.
func (c *Client) DoRequest(req *http.Request) ([]byte, error) {
err := c.RateLimiter.Wait(context.Background())
if err != nil {
Expand All @@ -140,7 +153,12 @@ func (c *Client) DoRequest(req *http.Request) ([]byte, error) {
if err != nil {
return nil, err
}
defer res.Body.Close()
defer func() {
if closeErr := res.Body.Close(); closeErr != nil {
// Log the error if you have a logger, otherwise this is acceptable for library code
_ = closeErr
}
}()

body, err := io.ReadAll(res.Body)
if err != nil {
Expand All @@ -154,6 +172,7 @@ func (c *Client) DoRequest(req *http.Request) ([]byte, error) {
return body, nil
}

// GetV1Url returns the base URL for CloudConnexa API v1 endpoints.
func (c *Client) GetV1Url() string {
return c.BaseURL + "/api/v1"
}
8 changes: 8 additions & 0 deletions cloudconnexa/cloudconnexa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"golang.org/x/time/rate"
)

// setupMockServer creates a test HTTP server that simulates the CloudConnexa API endpoints
// for testing purposes. It handles token authentication and basic endpoint responses.
func setupMockServer() *httptest.Server {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
Expand All @@ -36,6 +38,9 @@ func setupMockServer() *httptest.Server {
return server
}

// TestNewClient tests the creation of a new CloudConnexa client with various credential combinations.
// It verifies that the client is properly initialized with valid credentials and returns
// appropriate errors for invalid credentials.
func TestNewClient(t *testing.T) {
server := setupMockServer()
defer server.Close()
Expand Down Expand Up @@ -66,6 +71,9 @@ func TestNewClient(t *testing.T) {
}
}

// TestDoRequest tests the DoRequest method of the CloudConnexa client.
// It verifies that the client correctly handles various HTTP requests and responses,
// including valid requests, invalid endpoints, and incorrect HTTP methods.
func TestDoRequest(t *testing.T) {
server := setupMockServer()
defer server.Close()
Expand Down
9 changes: 9 additions & 0 deletions cloudconnexa/dns_records.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import (
)

var (
// ErrDNSRecordNotFound is returned when a DNS record is not found.
ErrDNSRecordNotFound = errors.New("dns record not found")
)

// DNSRecord represents a DNS record in CloudConnexa.
type DNSRecord struct {
ID string `json:"id"`
Domain string `json:"domain"`
Expand All @@ -20,6 +22,7 @@ type DNSRecord struct {
IPV6Addresses []string `json:"ipv6Addresses"`
}

// DNSRecordPageResponse represents a paginated response of DNS records.
type DNSRecordPageResponse struct {
Content []DNSRecord `json:"content"`
NumberOfElements int `json:"numberOfElements"`
Expand All @@ -30,8 +33,10 @@ type DNSRecordPageResponse struct {
TotalPages int `json:"totalPages"`
}

// DNSRecordsService provides methods for managing DNS records.
type DNSRecordsService service

// GetByPage retrieves DNS records using pagination.
func (c *DNSRecordsService) GetByPage(page int, pageSize int) (DNSRecordPageResponse, error) {
endpoint := fmt.Sprintf("%s/dns-records?page=%d&size=%d", c.client.GetV1Url(), page, pageSize)
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
Expand All @@ -52,6 +57,7 @@ func (c *DNSRecordsService) GetByPage(page int, pageSize int) (DNSRecordPageResp
return response, nil
}

// GetDNSRecord retrieves a specific DNS record by ID.
func (c *DNSRecordsService) GetDNSRecord(recordID string) (*DNSRecord, error) {
pageSize := 10
page := 0
Expand All @@ -76,6 +82,7 @@ func (c *DNSRecordsService) GetDNSRecord(recordID string) (*DNSRecord, error) {
return nil, ErrDNSRecordNotFound
}

// Create creates a new DNS record.
func (c *DNSRecordsService) Create(record DNSRecord) (*DNSRecord, error) {
recordJSON, err := json.Marshal(record)
if err != nil {
Expand All @@ -100,6 +107,7 @@ func (c *DNSRecordsService) Create(record DNSRecord) (*DNSRecord, error) {
return &d, nil
}

// Update updates an existing DNS record.
func (c *DNSRecordsService) Update(record DNSRecord) error {
recordJSON, err := json.Marshal(record)
if err != nil {
Expand All @@ -115,6 +123,7 @@ func (c *DNSRecordsService) Update(record DNSRecord) error {
return err
}

// Delete deletes a DNS record by ID.
func (c *DNSRecordsService) Delete(recordID string) error {
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/dns-records/%s", c.client.GetV1Url(), recordID), nil)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions cloudconnexa/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ package cloudconnexa

import "errors"

// ErrCredentialsRequired is returned when client ID or client secret is missing.
var ErrCredentialsRequired = errors.New("both client_id and client_secret credentials must be specified")
Loading