diff --git a/instance_ips.go b/instance_ips.go index 0d86bd91c..568be05eb 100644 --- a/instance_ips.go +++ b/instance_ips.go @@ -31,6 +31,7 @@ type InstanceIP struct { LinodeID int `json:"linode_id"` Region string `json:"region"` VPCNAT1To1 *InstanceIPNAT1To1 `json:"vpc_nat_1_1"` + Reserved bool `json:"reserved"` } // VPCIP represents a private IP address in a VPC subnet with additional networking details diff --git a/network_reserved_ips.go b/network_reserved_ips.go new file mode 100644 index 000000000..56f343f82 --- /dev/null +++ b/network_reserved_ips.go @@ -0,0 +1,54 @@ +package linodego + +import ( + "context" +) + +// ReserveIPOptions represents the options for reserving an IP address +// NOTE: Reserved IP feature may not currently be available to all users. +type ReserveIPOptions struct { + Region string `json:"region"` +} + +// ListReservedIPAddresses retrieves a list of reserved IP addresses +// NOTE: Reserved IP feature may not currently be available to all users. +func (c *Client) ListReservedIPAddresses(ctx context.Context, opts *ListOptions) ([]InstanceIP, error) { + e := formatAPIPath("networking/reserved/ips") + response, err := getPaginatedResults[InstanceIP](ctx, c, e, opts) + if err != nil { + return nil, err + } + + return response, nil +} + +// GetReservedIPAddress retrieves details of a specific reserved IP address +// NOTE: Reserved IP feature may not currently be available to all users. +func (c *Client) GetReservedIPAddress(ctx context.Context, ipAddress string) (*InstanceIP, error) { + e := formatAPIPath("networking/reserved/ips/%s", ipAddress) + response, err := doGETRequest[InstanceIP](ctx, c, e) + if err != nil { + return nil, err + } + + return response, nil +} + +// ReserveIPAddress reserves a new IP address +// NOTE: Reserved IP feature may not currently be available to all users. +func (c *Client) ReserveIPAddress(ctx context.Context, opts ReserveIPOptions) (*InstanceIP, error) { + e := "networking/reserved/ips" + response, err := doPOSTRequest[InstanceIP](ctx, c, e, opts) + if err != nil { + return nil, err + } + + return response, nil +} + +// DeleteReservedIPAddress deletes a reserved IP address +// NOTE: Reserved IP feature may not currently be available to all users. +func (c *Client) DeleteReservedIPAddress(ctx context.Context, ipAddress string) error { + e := formatAPIPath("networking/reserved/ips/%s", ipAddress) + return doDELETERequest(ctx, c, e) +} diff --git a/test/integration/fixtures/TestReservedIPAddresses_DeleteIPAddressVariants.yaml b/test/integration/fixtures/TestReservedIPAddresses_DeleteIPAddressVariants.yaml new file mode 100644 index 000000000..a033a961f --- /dev/null +++ b/test/integration/fixtures/TestReservedIPAddresses_DeleteIPAddressVariants.yaml @@ -0,0 +1,348 @@ +--- +version: 1 +interactions: +- request: + body: '{"region":"us-east"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips + method: POST + response: + body: '{"address": "66.175.209.178", "gateway": "66.175.209.1", "subnet_mask": + "255.255.255.0", "prefix": 24, "type": "ipv4", "public": true, "rdns": "66-175-209-178.ip.linodeusercontent.com", + "linode_id": null, "region": "us-east", "vpc_nat_1_1": null, "reserved": true}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "264" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:50 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "400" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips?page=1 + method: GET + response: + body: '{"data": [{"address": "66.175.209.178", "gateway": "66.175.209.1", "subnet_mask": + "255.255.255.0", "prefix": 24, "type": "ipv4", "public": true, "rdns": "66-175-209-178.ip.linodeusercontent.com", + "linode_id": null, "region": "us-east", "vpc_nat_1_1": null, "reserved": true}], + "page": 1, "pages": 1, "results": 1}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "313" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:50 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/66.175.209.178 + method: DELETE + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:50 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips?page=1 + method: GET + response: + body: '{"data": [], "page": 1, "pages": 1, "results": 0}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "49" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:50 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/66.175.209.178 + method: GET + response: + body: '{"errors": [{"reason": "Not found"}]}' + headers: + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "37" + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:51 GMT + Pragma: + - no-cache + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_only + X-Frame-Options: + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + status: 404 Not Found + code: 404 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/255.255.255.4 + method: DELETE + response: + body: '{"errors": [{"reason": "Not found"}]}' + headers: + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "37" + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:51 GMT + Pragma: + - no-cache + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Frame-Options: + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + status: 404 Not Found + code: 404 + duration: "" diff --git a/test/integration/fixtures/TestReservedIPAddresses_EndToEndTest.yaml b/test/integration/fixtures/TestReservedIPAddresses_EndToEndTest.yaml new file mode 100644 index 000000000..1dcbe2b27 --- /dev/null +++ b/test/integration/fixtures/TestReservedIPAddresses_EndToEndTest.yaml @@ -0,0 +1,428 @@ +--- +version: 1 +interactions: +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips?page=1 + method: GET + response: + body: '{"data": [], "page": 1, "pages": 1, "results": 0}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "49" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:47 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"region":"us-east"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips + method: POST + response: + body: '{"address": "66.175.209.178", "gateway": "66.175.209.1", "subnet_mask": + "255.255.255.0", "prefix": 24, "type": "ipv4", "public": true, "rdns": "66-175-209-178.ip.linodeusercontent.com", + "linode_id": null, "region": "us-east", "vpc_nat_1_1": null, "reserved": true}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "264" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:48 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "400" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/66.175.209.178 + method: GET + response: + body: '{"address": "66.175.209.178", "gateway": "66.175.209.1", "subnet_mask": + "255.255.255.0", "prefix": 24, "type": "ipv4", "public": true, "rdns": "66-175-209-178.ip.linodeusercontent.com", + "linode_id": null, "region": "us-east", "vpc_nat_1_1": null, "reserved": true}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "264" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:48 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips?page=1 + method: GET + response: + body: '{"data": [{"address": "66.175.209.178", "gateway": "66.175.209.1", "subnet_mask": + "255.255.255.0", "prefix": 24, "type": "ipv4", "public": true, "rdns": "66-175-209-178.ip.linodeusercontent.com", + "linode_id": null, "region": "us-east", "vpc_nat_1_1": null, "reserved": true}], + "page": 1, "pages": 1, "results": 1}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "313" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:48 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/66.175.209.178 + method: DELETE + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:49 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/66.175.209.178 + method: GET + response: + body: '{"errors": [{"reason": "Not found"}]}' + headers: + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "37" + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:49 GMT + Pragma: + - no-cache + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_only + X-Frame-Options: + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + status: 404 Not Found + code: 404 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips?page=1 + method: GET + response: + body: '{"data": [], "page": 1, "pages": 1, "results": 0}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "49" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:49 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" diff --git a/test/integration/fixtures/TestReservedIPAddresses_ExceedLimit.yaml b/test/integration/fixtures/TestReservedIPAddresses_ExceedLimit.yaml new file mode 100644 index 000000000..57449d247 --- /dev/null +++ b/test/integration/fixtures/TestReservedIPAddresses_ExceedLimit.yaml @@ -0,0 +1,296 @@ +--- +version: 1 +interactions: +- request: + body: '{"region":"us-east"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips + method: POST + response: + body: '{"address": "69.164.208.243", "gateway": "69.164.208.1", "subnet_mask": + "255.255.255.0", "prefix": 24, "type": "ipv4", "public": true, "rdns": "69-164-208-243.ip.linodeusercontent.com", + "linode_id": null, "region": "us-east", "vpc_nat_1_1": null, "reserved": true}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "264" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Mon, 16 Sep 2024 17:00:22 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "800" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"region":"us-east"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips + method: POST + response: + body: '{"address": "45.79.130.88", "gateway": "45.79.130.1", "subnet_mask": "255.255.255.0", + "prefix": 24, "type": "ipv4", "public": true, "rdns": "45-79-130-88.ip.linodeusercontent.com", + "linode_id": null, "region": "us-east", "vpc_nat_1_1": null, "reserved": true}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "259" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Mon, 16 Sep 2024 17:00:22 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "800" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"region":"us-east"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips + method: POST + response: + body: '{"errors": [{"reason": "Additional Reserved IPv4 addresses require technical + justification. Please contact support describing your requirement."}]}' + headers: + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Content-Length: + - "147" + Content-Type: + - application/json + Expires: + - Mon, 16 Sep 2024 17:00:22 GMT + Pragma: + - no-cache + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Frame-Options: + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "800" + status: 400 Bad Request + code: 400 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/69.164.208.243 + method: DELETE + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Mon, 16 Sep 2024 17:00:22 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/45.79.130.88 + method: DELETE + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Mon, 16 Sep 2024 17:00:23 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" diff --git a/test/integration/fixtures/TestReservedIPAddresses_GetIPAddressVariants.yaml b/test/integration/fixtures/TestReservedIPAddresses_GetIPAddressVariants.yaml new file mode 100644 index 000000000..6a4bf9cd1 --- /dev/null +++ b/test/integration/fixtures/TestReservedIPAddresses_GetIPAddressVariants.yaml @@ -0,0 +1,239 @@ +--- +version: 1 +interactions: +- request: + body: '{"region":"us-east"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips + method: POST + response: + body: '{"address": "45.56.109.241", "gateway": "45.56.109.1", "subnet_mask": "255.255.255.0", + "prefix": 24, "type": "ipv4", "public": true, "rdns": "45-56-109-241.ip.linodeusercontent.com", + "linode_id": null, "region": "us-east", "vpc_nat_1_1": null, "reserved": true}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "261" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 29 Aug 2024 15:39:20 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "400" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/45.56.109.241 + method: GET + response: + body: '{"address": "45.56.109.241", "gateway": "45.56.109.1", "subnet_mask": "255.255.255.0", + "prefix": 24, "type": "ipv4", "public": true, "rdns": "45-56-109-241.ip.linodeusercontent.com", + "linode_id": null, "region": "us-east", "vpc_nat_1_1": null, "reserved": true}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "261" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 29 Aug 2024 15:39:20 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/999.999.999.999 + method: GET + response: + body: '{"errors": [{"reason": "Not found"}]}' + headers: + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "37" + Content-Type: + - application/json + Expires: + - Thu, 29 Aug 2024 15:39:20 GMT + Pragma: + - no-cache + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_only + X-Frame-Options: + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + status: 404 Not Found + code: 404 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/45.56.109.241 + method: DELETE + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 29 Aug 2024 15:39:21 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" diff --git a/test/integration/fixtures/TestReservedIPAddresses_InsufficientPermissions.yaml b/test/integration/fixtures/TestReservedIPAddresses_InsufficientPermissions.yaml new file mode 100644 index 000000000..764d3ac0b --- /dev/null +++ b/test/integration/fixtures/TestReservedIPAddresses_InsufficientPermissions.yaml @@ -0,0 +1,185 @@ +--- +version: 1 +interactions: +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips?page=1 + method: GET + response: + body: '{"errors": [{"reason": "Account doesn''t have permission to access the + ''Reserved IPs'' feature."}]}' + headers: + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "97" + Content-Type: + - application/json + Expires: + - Thu, 22 Aug 2024 15:37:02 GMT + Pragma: + - no-cache + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_only + X-Frame-Options: + - DENY + X-Oauth-Scopes: + - '*' + status: 404 Not Found + code: 404 + duration: "" +- request: + body: '{"region":"us-east"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips + method: POST + response: + body: '{"errors": [{"reason": "Account doesn''t have permission to access the + ''Reserved IPs'' feature."}]}' + headers: + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Content-Length: + - "97" + Content-Type: + - application/json + Expires: + - Thu, 22 Aug 2024 15:37:02 GMT + Pragma: + - no-cache + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Frame-Options: + - DENY + X-Oauth-Scopes: + - '*' + status: 404 Not Found + code: 404 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/172.28.3.4 + method: GET + response: + body: '{"errors": [{"reason": "Account doesn''t have permission to access the + ''Reserved IPs'' feature."}]}' + headers: + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "97" + Content-Type: + - application/json + Expires: + - Thu, 22 Aug 2024 15:37:03 GMT + Pragma: + - no-cache + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_only + X-Frame-Options: + - DENY + X-Oauth-Scopes: + - '*' + status: 404 Not Found + code: 404 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/172.28.3.4 + method: DELETE + response: + body: '{"errors": [{"reason": "Account doesn''t have permission to access the + ''Reserved IPs'' feature."}]}' + headers: + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "97" + Content-Type: + - application/json + Expires: + - Thu, 22 Aug 2024 15:37:03 GMT + Pragma: + - no-cache + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Frame-Options: + - DENY + X-Oauth-Scopes: + - '*' + status: 404 Not Found + code: 404 + duration: "" diff --git a/test/integration/fixtures/TestReservedIPAddresses_ListIPAddressesVariants.yaml b/test/integration/fixtures/TestReservedIPAddresses_ListIPAddressesVariants.yaml new file mode 100644 index 000000000..bffe12226 --- /dev/null +++ b/test/integration/fixtures/TestReservedIPAddresses_ListIPAddressesVariants.yaml @@ -0,0 +1,321 @@ +--- +version: 1 +interactions: +- request: + body: '{"region":"us-east"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips + method: POST + response: + body: '{"address": "66.175.209.108", "gateway": "66.175.209.1", "subnet_mask": + "255.255.255.0", "prefix": 24, "type": "ipv4", "public": true, "rdns": "66-175-209-108.ip.linodeusercontent.com", + "linode_id": null, "region": "us-east", "vpc_nat_1_1": null, "reserved": true}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "264" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:49 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "400" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"region":"us-east"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips + method: POST + response: + body: '{"address": "66.175.209.141", "gateway": "66.175.209.1", "subnet_mask": + "255.255.255.0", "prefix": 24, "type": "ipv4", "public": true, "rdns": "66-175-209-141.ip.linodeusercontent.com", + "linode_id": null, "region": "us-east", "vpc_nat_1_1": null, "reserved": true}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "264" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:49 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "400" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + X-Filter: + - '{"reserved":true,"region":"us-east"}' + url: https://api.linode.com/v4beta/networking/ips?page=1 + method: GET + response: + body: '{"page": 1, "pages": 1, "results": 2, "data": [{"address": "66.175.209.108", + "gateway": "66.175.209.1", "subnet_mask": "255.255.255.0", "prefix": 24, "type": + "ipv4", "public": true, "rdns": "66-175-209-108.ip.linodeusercontent.com", "linode_id": + null, "region": "us-east", "vpc_nat_1_1": null, "reserved": true}, {"address": + "66.175.209.141", "gateway": "66.175.209.1", "subnet_mask": "255.255.255.0", + "prefix": 24, "type": "ipv4", "public": true, "rdns": "66-175-209-141.ip.linodeusercontent.com", + "linode_id": null, "region": "us-east", "vpc_nat_1_1": null, "reserved": true}]}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "579" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:49 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "400" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/66.175.209.108 + method: DELETE + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:50 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/66.175.209.141 + method: DELETE + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:52:50 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" diff --git a/test/integration/fixtures/TestReservedIPAddresses_ReserveIPVariants.yaml b/test/integration/fixtures/TestReservedIPAddresses_ReserveIPVariants.yaml new file mode 100644 index 000000000..2ff00f7a0 --- /dev/null +++ b/test/integration/fixtures/TestReservedIPAddresses_ReserveIPVariants.yaml @@ -0,0 +1,383 @@ +--- +version: 1 +interactions: +- request: + body: '{"region":""}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips + method: POST + response: + body: '{"errors": [{"reason": "region is not valid", "field": "region"}]}' + headers: + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Content-Length: + - "66" + Content-Type: + - application/json + Expires: + - Mon, 16 Sep 2024 17:00:20 GMT + Pragma: + - no-cache + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Frame-Options: + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "800" + status: 400 Bad Request + code: 400 + duration: "" +- request: + body: '{"region":"us"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips + method: POST + response: + body: '{"errors": [{"reason": "region is not valid", "field": "region"}]}' + headers: + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Content-Length: + - "66" + Content-Type: + - application/json + Expires: + - Mon, 16 Sep 2024 17:00:20 GMT + Pragma: + - no-cache + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Frame-Options: + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "800" + status: 400 Bad Request + code: 400 + duration: "" +- request: + body: '{"region":""}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips + method: POST + response: + body: '{"errors": [{"reason": "region is not valid", "field": "region"}]}' + headers: + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Content-Length: + - "66" + Content-Type: + - application/json + Expires: + - Mon, 16 Sep 2024 17:00:20 GMT + Pragma: + - no-cache + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Frame-Options: + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "800" + status: 400 Bad Request + code: 400 + duration: "" +- request: + body: '{"region":"us-east"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips + method: POST + response: + body: '{"address": "69.164.208.243", "gateway": "69.164.208.1", "subnet_mask": + "255.255.255.0", "prefix": 24, "type": "ipv4", "public": true, "rdns": "69-164-208-243.ip.linodeusercontent.com", + "linode_id": null, "region": "us-east", "vpc_nat_1_1": null, "reserved": true}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "264" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Mon, 16 Sep 2024 17:00:21 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "800" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"region":"us-east"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips + method: POST + response: + body: '{"address": "45.79.130.88", "gateway": "45.79.130.1", "subnet_mask": "255.255.255.0", + "prefix": 24, "type": "ipv4", "public": true, "rdns": "45-79-130-88.ip.linodeusercontent.com", + "linode_id": null, "region": "us-east", "vpc_nat_1_1": null, "reserved": true}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "259" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Mon, 16 Sep 2024 17:00:21 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "800" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/69.164.208.243 + method: DELETE + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Mon, 16 Sep 2024 17:00:21 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego/dev https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/reserved/ips/45.79.130.88 + method: DELETE + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Akamai-Internal-Account: + - '*' + Cache-Control: + - max-age=0, no-cache, no-store + Connection: + - keep-alive + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Mon, 16 Sep 2024 17:00:21 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - ips:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "10" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" diff --git a/test/integration/network_reserved_ips_test.go b/test/integration/network_reserved_ips_test.go new file mode 100644 index 000000000..a9b8c8b88 --- /dev/null +++ b/test/integration/network_reserved_ips_test.go @@ -0,0 +1,393 @@ +package integration + +import ( + "context" + "strings" + + "testing" + + "github.com/linode/linodego" + . "github.com/linode/linodego" +) + +// TestReservedIPAddresses_InsufficientPermissions tests the behavior when a user account +// doesn't have the permission to use the Reserved IP feature +func TestReservedIPAddresses_InsufficientPermissions(t *testing.T) { + original := validTestAPIKey + dummyToken := "badtoken" + validTestAPIKey = dummyToken + + client, teardown := createTestClient(t, "fixtures/TestReservedIPAddresses_InsufficientPermissions") + defer teardown() + defer func() { validTestAPIKey = original }() + + filter := "" + ips, listErr := client.ListReservedIPAddresses(context.Background(), NewListOptions(0, filter)) + if listErr == nil { + t.Errorf("Expected error due to insufficient permissions, but got none %v", ips) + } else { + t.Logf("Correctly received error when listing IP addresses: %v", listErr) + } + + if len(ips) != 0 { + t.Errorf("Expected no IP addresses due to insufficient permissions, but got some: %v", ips) + } + + // Attempt to reserve an IP address + resIP, resErr := client.ReserveIPAddress(context.Background(), ReserveIPOptions{ + Region: "us-east", + }) + if resErr == nil { + t.Errorf("Expected error when reserving IP due to insufficient permissions, but got none") + } else { + t.Logf("Correctly received %v and error when reserving IP: %v", resIP, resErr) + } + + // Attempt to get a reserved IP address + address := "172.28.3.4" + ip, getErr := client.GetReservedIPAddress(context.Background(), address) + if getErr == nil { + t.Errorf("Expected error when getting IP address due to insufficient permissions, but got none") + } else { + t.Logf("Correctly received %v for IP Address and error when getting IP address: %v", ip, getErr) + } + + // Attempt to delete a reserved IP address + delAddr := "172.28.3.4" + delErr := client.DeleteReservedIPAddress(context.Background(), delAddr) + if delErr == nil { + t.Errorf("Expected error when deleting IP address due to insufficient permissions, but got none") + } else { + t.Logf("Correctly received error when deleting IP address: %v", delErr) + } +} + +// TestReservedIPAddresses_EndToEndTest performs an end-to-end test of the Reserved IP functionality +// for users with the can_reserve_ip flag enabled +func TestReservedIPAddresses_EndToEndTest(t *testing.T) { + client, teardown := createTestClient(t, "fixtures/TestReservedIPAddresses_EndToEndTest") + defer teardown() + + filter := "" + + ipList, err := client.ListReservedIPAddresses(context.Background(), NewListOptions(0, filter)) + + if err != nil { + t.Fatalf("Error listing IP addresses: %v", err) + } + + initialCount := len(ipList) + + // Attempt to reserve an IP + resIP, resErr := client.ReserveIPAddress(context.Background(), ReserveIPOptions{ + Region: "us-east", + }) + + if resErr != nil { + t.Fatalf("Failed to reserve IP. This test expects the user to have 0 prior reservations and the ip_reservation_limit to be 2. Error from the API: %v", resErr) + } + + t.Logf("Successfully reserved IP: %+v", resIP) + + // Fetch the reserved IP + fetchedIP, fetchErr := client.GetReservedIPAddress(context.Background(), resIP.Address) + if fetchErr != nil { + t.Errorf("Error getting reserved IP address: %v", fetchErr) + } + + if fetchedIP == nil { + t.Errorf("Expected %s but got nil indicating a failure in fetching the reserved IP", resIP.Address) + } + + // Verify the list of IPs has increased + verifyList, verifyErr := client.ListReservedIPAddresses(context.Background(), NewListOptions(0, filter)) + if verifyErr != nil { + t.Fatalf("Error listing IP addresses after reservation: %v", verifyErr) + } + + if len(verifyList) != initialCount+1 { + t.Errorf("Expected IP count to increase by 1, got %d, want %d", len(verifyList), initialCount+1) + } + + // Delete the reserved IP + delErr := client.DeleteReservedIPAddress(context.Background(), resIP.Address) + if delErr != nil { + t.Fatalf("Error deleting reserved IP address: %v", delErr) + } + + // Verify the IP has been deleted + _, fetchDelErr := client.GetReservedIPAddress(context.Background(), resIP.Address) + if fetchDelErr == nil { + t.Errorf("Expected error when fetching %s, got nil", resIP.Address) + } + + verifyDelList, verifyDelErr := client.ListReservedIPAddresses(context.Background(), NewListOptions(0, filter)) + if verifyDelErr != nil { + t.Fatalf("Error listing IP addresses after deletion: %v", verifyDelErr) + } + + if len(verifyDelList) != initialCount { + t.Errorf("Expected IP count to return to initial count, got %d, want %d", len(verifyDelList), initialCount) + } +} + +// TestReservedIPAddresses_ListIPAddressesVariants tests filters for listing IP addresses +func TestReservedIPAddresses_ListIPAddressesVariants(t *testing.T) { + client, teardown := createTestClient(t, "fixtures/TestReservedIPAddresses_ListIPAddressesVariants") + defer teardown() + + expected_ips := 2 + + // Reserve two IP addresses in us-east region + reservedIPs := make([]string, expected_ips) + for i := 0; i < expected_ips; i++ { + reserveIP, err := client.ReserveIPAddress(context.Background(), linodego.ReserveIPOptions{ + Region: "us-east", + }) + if err != nil { + t.Fatalf("Failed to reserve IP %d: %v", i+1, err) + } + reservedIPs[i] = reserveIP.Address + t.Logf("Successfully reserved IP %d: %s", i+1, reserveIP.Address) + } + + // Defer cleanup of reserved IPs + defer func() { + for _, ip := range reservedIPs { + err := client.DeleteReservedIPAddress(context.Background(), ip) + if err != nil { + t.Errorf("Failed to delete reserved IP %s: %v", ip, err) + } + } + }() + + // Create ListOptions with the filter for reserved IPs in us-east region + listOptions := linodego.ListOptions{ + PageOptions: &linodego.PageOptions{ + Page: 0, + }, + Filter: "{\"reserved\":true,\"region\":\"us-east\"}", + } + + ipList, err := client.ListIPAddresses(context.Background(), &listOptions) + + if err != nil { + t.Fatalf("Error listing reserved IP addresses in us-east: %v", err) + } + + t.Logf("Retrieved %d reserved IP addresses in us-east", len(ipList)) + + // Check if at least the two reserved IPs are in the list + foundReservedIPs := 0 + for _, ip := range ipList { + if !ip.Reserved { + t.Errorf("Expected all IPs to be reserved, but found non-reserved IP: %s", ip.Address) + } + if ip.Region != "us-east" { + t.Errorf("Expected all IPs to be in us-east region, but found IP in %s region: %s", ip.Region, ip.Address) + } + for _, reservedIP := range reservedIPs { + if ip.Address == reservedIP { + foundReservedIPs++ + break + } + } + } + if foundReservedIPs != expected_ips { + t.Errorf("Expected %d but found %d while listing reserved IP addresses", expected_ips, foundReservedIPs) + } + +} + +// TestReservedIPAddresses_GetIPAddressVariants tests various scenarios for getting a specific IP address +func TestReservedIPAddresses_GetIPAddressVariants(t *testing.T) { + client, teardown := createTestClient(t, "fixtures/TestReservedIPAddresses_GetIPAddressVariants") + defer teardown() + + // Reserve an IP for testing + resIP, resErr := client.ReserveIPAddress(context.Background(), ReserveIPOptions{ + Region: "us-east", + }) + + if resErr != nil { + t.Fatalf("Failed to reserve IP. This test expects the user to have 0 prior reservations and the ip_reservation_limit to be 2. Error from the API: %v", resErr) + } + + if resIP == nil { + t.Fatalf("Reserved IP is nil") + } + + t.Logf("Successfully reserved IP: %+v", resIP) + + // Test getting a valid reserved IP + validIP, fetchErr := client.GetReservedIPAddress(context.Background(), resIP.Address) + if fetchErr != nil { + t.Errorf("Error getting valid reserved IP address: %v", fetchErr) + } + + if validIP == nil { + t.Errorf("Retrieved valid reserved IP is nil") + } else { + if validIP.Address != resIP.Address { + t.Errorf("Retrieved IP address does not match reserved IP address. Got %s, want %s", validIP.Address, resIP.Address) + } + } + + // Test getting an invalid IP + invalidIP := "999.999.999.999" + _, invalidFetchErr := client.GetReservedIPAddress(context.Background(), invalidIP) + if invalidFetchErr == nil { + t.Errorf("Expected error when fetching invalid IP, got nil") + } + + // Clean up: Delete the reserved IP + delErr := client.DeleteReservedIPAddress(context.Background(), resIP.Address) + if delErr != nil { + t.Errorf("Failed to delete reserved IP: %v", delErr) + } +} + +// TestReservedIPAddresses_ReserveIPVariants tests various scenarios for reserving an IP address +func TestReservedIPAddresses_ReserveIPAddressVariants(t *testing.T) { + client, teardown := createTestClient(t, "fixtures/TestReservedIPAddresses_ReserveIPVariants") + defer teardown() + + // Slice to keep track of all reserved IPs + var reservedIPs []string + + // Helper function to clean up reserved IPs + cleanupIPs := func() { + for _, ip := range reservedIPs { + err := client.DeleteReservedIPAddress(context.Background(), ip) + if err != nil { + t.Errorf("Failed to delete reserved IP %s: %v", ip, err) + } + } + } + defer cleanupIPs() + + // Test reserving IP with omitted region + _, omitErr := client.ReserveIPAddress(context.Background(), ReserveIPOptions{}) + if omitErr == nil { + t.Errorf("Expected error when reserving IP with omitted region, got nil") + } + + // Test reserving IP with invalid region + _, invalidErr := client.ReserveIPAddress(context.Background(), ReserveIPOptions{Region: "us"}) + if invalidErr == nil { + t.Errorf("Expected error when reserving IP with invalid region, got nil") + } + + // Test reserving IP with empty region + _, emptyErr := client.ReserveIPAddress(context.Background(), ReserveIPOptions{Region: ""}) + if emptyErr == nil { + t.Errorf("Expected error when reserving IP with empty region, got nil") + } + + // Make 2 valid IP Reservations + for i := 0; i < 2; i++ { + reserveIP, err := client.ReserveIPAddress(context.Background(), linodego.ReserveIPOptions{ + Region: "us-east", + }) + if err != nil { + t.Fatalf("Failed to reserve IP %d: %v", i+1, err) + } + reservedIPs = append(reservedIPs, reserveIP.Address) + t.Logf("Successfully reserved IP %d: %s", i+1, reserveIP.Address) + } +} + +func TestReservedIPAddresses_ExceedLimit(t *testing.T) { + client, teardown := createTestClient(t, "fixtures/TestReservedIPAddresses_ExceedLimit") + defer teardown() + + // Slice to keep track of all reserved IPs + var reservedIPs []string + + // Helper function to clean up reserved IPs + cleanupIPs := func() { + for _, ip := range reservedIPs { + err := client.DeleteReservedIPAddress(context.Background(), ip) + if err != nil { + t.Errorf("Failed to delete reserved IP %s: %v", ip, err) + } + } + } + defer cleanupIPs() + + // Reserve IPs until the limit is reached and assert the error message + for i := 0; i < 100; i++ { + reservedIP, err := client.ReserveIPAddress(context.Background(), linodego.ReserveIPOptions{ + Region: "us-east", + }) + if err != nil { + expectedErrorMessage := "[400] Additional Reserved IPv4 addresses require technical justification." + if !strings.Contains(err.Error(), expectedErrorMessage) { + t.Errorf("Expected error message to contain '%s', but got: %v", expectedErrorMessage, err) + } else { + t.Logf("Failed to reserve IP %d as expected: %v", i+1, err) + } + break + } + + reservedIPs = append(reservedIPs, reservedIP.Address) + + if i == 99 { + t.Errorf("Expected to hit reservation limit, but did not reach it after 100 attempts") + } + } +} + +// TestReservedIPAddresses_DeleteIPAddressVariants tests various scenarios for deleting a reserved IP address +func TestReservedIPAddresses_DeleteIPAddressVariants(t *testing.T) { + client, teardown := createTestClient(t, "fixtures/TestReservedIPAddresses_DeleteIPAddressVariants") + defer teardown() + + validRes, validErr := client.ReserveIPAddress(context.Background(), ReserveIPOptions{Region: "us-east"}) + if validErr != nil { + t.Fatalf("Failed to reserve IP. This test should start with 0 reservations or reservations < limit. Error from the API: %v", validErr) + } + + if validRes == nil { + t.Fatalf("Valid reservation returned nil IP") + } + + t.Logf("Successfully reserved IP: %+v", validRes) + + filter := "" + ipList, listErr := client.ListReservedIPAddresses(context.Background(), NewListOptions(0, filter)) + if listErr != nil { + t.Fatalf("Error listing IP addresses: %v", listErr) + } + + if len(ipList) == 0 { + t.Fatalf("No reserved IPs available for testing deletion") + } + + delErr := client.DeleteReservedIPAddress(context.Background(), validRes.Address) + if delErr != nil { + t.Fatalf("Failed to delete reserved IP address: %v", delErr) + } + + // Verify deletion + verifyDelList, verifyDelErr := client.ListReservedIPAddresses(context.Background(), NewListOptions(0, filter)) + if verifyDelErr != nil { + t.Fatalf("Error listing IP addresses after deletion: %v", verifyDelErr) + } + + if len(verifyDelList) >= len(ipList) { + t.Errorf("IP address deletion not confirmed. Expected count < %d, got %d", len(ipList), len(verifyDelList)) + } + + _, fetchDelErr := client.GetReservedIPAddress(context.Background(), validRes.Address) + if fetchDelErr == nil { + t.Errorf("Expected error when fetching deleted IP, got nil") + } + + // Test deleting an unowned IP + unownedIP := "255.255.255.4" + delUnownedErr := client.DeleteReservedIPAddress(context.Background(), unownedIP) + if delUnownedErr == nil { + t.Errorf("Expected error when deleting unowned IP, got nil") + } +}