diff --git a/base_types.go b/base_types.go new file mode 100644 index 000000000..dbaafe317 --- /dev/null +++ b/base_types.go @@ -0,0 +1,29 @@ +// This package contains various type-related base classes intended +// to be used in composition across type structures in this project. + +package linodego + +// baseType is a base struct containing the core fields of a resource type +// returned from the Linode API. +type baseType[PriceType any, RegionPriceType any] struct { + ID string `json:"id"` + Label string `json:"label"` + Price PriceType `json:"price"` + RegionPrices []RegionPriceType `json:"region_prices"` + Transfer int `json:"transfer"` +} + +// baseTypePrice is a base struct containing the core fields of a resource type's +// base price. +type baseTypePrice struct { + Hourly float64 `json:"hourly"` + Monthly float64 `json:"monthly"` +} + +// baseTypeRegionPrice is a base struct containing the core fields of a resource type's +// region-specific price. +type baseTypeRegionPrice struct { + baseTypePrice + + ID string `json:"id"` +} diff --git a/lke_types.go b/lke_types.go new file mode 100644 index 000000000..14273bbd1 --- /dev/null +++ b/lke_types.go @@ -0,0 +1,47 @@ +package linodego + +import ( + "context" +) + +// LKEType represents a single valid LKE type. +// NOTE: This typically corresponds to the availability of a cluster's +// control plane. +type LKEType struct { + baseType[LKETypePrice, LKETypeRegionPrice] +} + +// LKETypePrice represents the base hourly and monthly prices +// for an LKE type entry. +type LKETypePrice struct { + baseTypePrice +} + +// LKETypeRegionPrice represents the regional hourly and monthly prices +// for an LKE type entry. +type LKETypeRegionPrice struct { + baseTypeRegionPrice +} + +// ListLKETypes lists LKE types. This endpoint is cached by default. +func (c *Client) ListLKETypes(ctx context.Context, opts *ListOptions) ([]LKEType, error) { + e := "lke/types" + + endpoint, err := generateListCacheURL(e, opts) + if err != nil { + return nil, err + } + + if result := c.getCachedResponse(endpoint); result != nil { + return result.([]LKEType), nil + } + + response, err := getPaginatedResults[LKEType](ctx, c, e, opts) + if err != nil { + return nil, err + } + + c.addCachedResponse(endpoint, response, &cacheExpiryTime) + + return response, nil +} diff --git a/network_transfer_prices.go b/network_transfer_prices.go new file mode 100644 index 000000000..daa25e885 --- /dev/null +++ b/network_transfer_prices.go @@ -0,0 +1,45 @@ +package linodego + +import ( + "context" +) + +// NetworkTransferPrice represents a single valid network transfer price. +type NetworkTransferPrice struct { + baseType[NetworkTransferTypePrice, NetworkTransferTypeRegionPrice] +} + +// NetworkTransferTypePrice represents the base hourly and monthly prices +// for a network transfer price entry. +type NetworkTransferTypePrice struct { + baseTypePrice +} + +// NetworkTransferTypeRegionPrice represents the regional hourly and monthly prices +// for a network transfer price entry. +type NetworkTransferTypeRegionPrice struct { + baseTypeRegionPrice +} + +// ListNetworkTransferPrices lists network transfer prices. This endpoint is cached by default. +func (c *Client) ListNetworkTransferPrices(ctx context.Context, opts *ListOptions) ([]NetworkTransferPrice, error) { + e := "network-transfer/prices" + + endpoint, err := generateListCacheURL(e, opts) + if err != nil { + return nil, err + } + + if result := c.getCachedResponse(endpoint); result != nil { + return result.([]NetworkTransferPrice), nil + } + + response, err := getPaginatedResults[NetworkTransferPrice](ctx, c, e, opts) + if err != nil { + return nil, err + } + + c.addCachedResponse(endpoint, response, &cacheExpiryTime) + + return response, nil +} diff --git a/nodebalancer_types.go b/nodebalancer_types.go new file mode 100644 index 000000000..879665d43 --- /dev/null +++ b/nodebalancer_types.go @@ -0,0 +1,45 @@ +package linodego + +import ( + "context" +) + +// NodeBalancerType represents a single valid NodeBalancer type. +type NodeBalancerType struct { + baseType[NodeBalancerTypePrice, NodeBalancerTypeRegionPrice] +} + +// NodeBalancerTypePrice represents the base hourly and monthly prices +// for a NodeBalancer type entry. +type NodeBalancerTypePrice struct { + baseTypePrice +} + +// NodeBalancerTypeRegionPrice represents the regional hourly and monthly prices +// for a NodeBalancer type entry. +type NodeBalancerTypeRegionPrice struct { + baseTypeRegionPrice +} + +// ListNodeBalancerTypes lists NodeBalancer types. This endpoint is cached by default. +func (c *Client) ListNodeBalancerTypes(ctx context.Context, opts *ListOptions) ([]NodeBalancerType, error) { + e := "nodebalancers/types" + + endpoint, err := generateListCacheURL(e, opts) + if err != nil { + return nil, err + } + + if result := c.getCachedResponse(endpoint); result != nil { + return result.([]NodeBalancerType), nil + } + + response, err := getPaginatedResults[NodeBalancerType](ctx, c, e, opts) + if err != nil { + return nil, err + } + + c.addCachedResponse(endpoint, response, &cacheExpiryTime) + + return response, nil +} diff --git a/test/integration/fixtures/TestLKEType_List.yaml b/test/integration/fixtures/TestLKEType_List.yaml new file mode 100644 index 000000000..8025faeef --- /dev/null +++ b/test/integration/fixtures/TestLKEType_List.yaml @@ -0,0 +1,70 @@ +--- +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/lke/types?page=1 + method: GET + response: + body: '{"data": [{"id": "lke-sa", "label": "LKE Standard Availability", "price": + {"hourly": 0.0, "monthly": 0.0}, "region_prices": [], "transfer": 0}, {"id": + "lke-ha", "label": "LKE High Availability", "price": {"hourly": 0.09, "monthly": + 60.0}, "region_prices": [{"id": "id-cgk", "hourly": 0.108, "monthly": 72.0}, + {"id": "br-gru", "hourly": 0.126, "monthly": 84.0}], "transfer": 0}], "page": + 1, "pages": 1, "results": 2}' + 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: + - "415" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Thu, 05 Sep 2024 17:47:57 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - '*' + 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: "" diff --git a/test/integration/fixtures/TestNetworkTransferPrice_List.yaml b/test/integration/fixtures/TestNetworkTransferPrice_List.yaml new file mode 100644 index 000000000..6ac929255 --- /dev/null +++ b/test/integration/fixtures/TestNetworkTransferPrice_List.yaml @@ -0,0 +1,70 @@ +--- +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/network-transfer/prices?page=1 + method: GET + response: + body: '{"data": [{"id": "distributed_network_transfer", "label": "Distributed + Network Transfer", "price": {"hourly": 0.01, "monthly": null}, "region_prices": + [], "transfer": 0}, {"id": "network_transfer", "label": "Network Transfer", + "price": {"hourly": 0.005, "monthly": null}, "region_prices": [{"id": "id-cgk", + "hourly": 0.015, "monthly": null}, {"id": "br-gru", "hourly": 0.007, "monthly": + null}], "transfer": 0}], "page": 1, "pages": 1, "results": 2}' + 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: + - "448" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Wed, 04 Sep 2024 18:06:01 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - '*' + 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: "" diff --git a/test/integration/fixtures/TestNodeBalancerType_List.yaml b/test/integration/fixtures/TestNodeBalancerType_List.yaml new file mode 100644 index 000000000..e05e369b4 --- /dev/null +++ b/test/integration/fixtures/TestNodeBalancerType_List.yaml @@ -0,0 +1,68 @@ +--- +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/nodebalancers/types?page=1 + method: GET + response: + body: '{"data": [{"id": "nodebalancer", "label": "NodeBalancer", "price": {"hourly": + 0.015, "monthly": 10.0}, "region_prices": [{"id": "id-cgk", "hourly": 0.018, + "monthly": 12.0}, {"id": "br-gru", "hourly": 0.021, "monthly": 14.0}], "transfer": + 0}], "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: + - "279" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Wed, 04 Sep 2024 17:52:02 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - '*' + 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: "" diff --git a/test/integration/fixtures/TestVolumeType_List.yaml b/test/integration/fixtures/TestVolumeType_List.yaml new file mode 100644 index 000000000..25e3c3247 --- /dev/null +++ b/test/integration/fixtures/TestVolumeType_List.yaml @@ -0,0 +1,68 @@ +--- +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/volumes/types?page=1 + method: GET + response: + body: '{"data": [{"id": "volume", "label": "Storage Volume", "price": {"hourly": + 0.00015, "monthly": 0.1}, "region_prices": [{"id": "id-cgk", "hourly": 0.00018, + "monthly": 0.12}, {"id": "br-gru", "hourly": 0.00021, "monthly": 0.14}], "transfer": + 0}], "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: + - "280" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Expires: + - Wed, 04 Sep 2024 17:59:26 GMT + Pragma: + - no-cache + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - '*' + 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: "" diff --git a/test/integration/lke_types_test.go b/test/integration/lke_types_test.go new file mode 100644 index 000000000..1095e9cd9 --- /dev/null +++ b/test/integration/lke_types_test.go @@ -0,0 +1,43 @@ +package integration + +import ( + "context" + "testing" + + "github.com/linode/linodego" + "github.com/stretchr/testify/require" +) + +func TestLKEType_List(t *testing.T) { + client, teardown := createTestClient(t, "fixtures/TestLKEType_List") + defer teardown() + + lkeTypes, err := client.ListLKETypes(context.Background(), nil) + require.NoError(t, err) + require.Greater(t, len(lkeTypes), 0) + + for _, lkeType := range lkeTypes { + validateLKEType(t, lkeType) + } +} + +func validateLKEType( + t *testing.T, + lkeType linodego.LKEType, +) { + require.NotEmpty(t, lkeType.ID) + require.NotEmpty(t, lkeType.Label) + + // NOTE: We use >= 0 here because this is treated as an additional + // cost on top of the base LKE cluster price, meaning SA has its + // prices set to 0. + require.GreaterOrEqual(t, lkeType.Price.Hourly, 0.0) + require.GreaterOrEqual(t, lkeType.Price.Monthly, 0.0) + require.GreaterOrEqual(t, lkeType.Transfer, 0) + + for _, regionPrice := range lkeType.RegionPrices { + require.NotEmpty(t, regionPrice.ID) + require.GreaterOrEqual(t, regionPrice.Hourly, 0.0) + require.GreaterOrEqual(t, regionPrice.Monthly, 0.0) + } +} diff --git a/test/integration/network_transfer_prices_test.go b/test/integration/network_transfer_prices_test.go new file mode 100644 index 000000000..641b7c4ea --- /dev/null +++ b/test/integration/network_transfer_prices_test.go @@ -0,0 +1,40 @@ +package integration + +import ( + "context" + "testing" + + "github.com/linode/linodego" + "github.com/stretchr/testify/require" +) + +func TestNetworkTransferPrice_List(t *testing.T) { + client, teardown := createTestClient(t, "fixtures/TestNetworkTransferPrice_List") + defer teardown() + + prices, err := client.ListNetworkTransferPrices(context.Background(), nil) + require.NoError(t, err) + require.Greater(t, len(prices), 0) + + for _, price := range prices { + validateNetworkTransferPrice(t, price) + } +} + +func validateNetworkTransferPrice( + t *testing.T, + price linodego.NetworkTransferPrice, +) { + require.NotEmpty(t, price.ID) + require.NotEmpty(t, price.Label) + + // NOTE: We do not check for monthly prices here because it is + // explicitly set to null. + require.Greater(t, price.Price.Hourly, 0.0) + require.GreaterOrEqual(t, price.Transfer, 0) + + for _, regionPrice := range price.RegionPrices { + require.NotEmpty(t, regionPrice.ID) + require.Greater(t, regionPrice.Hourly, 0.0) + } +} diff --git a/test/integration/nodebalancer_types_test.go b/test/integration/nodebalancer_types_test.go new file mode 100644 index 000000000..f764e8e33 --- /dev/null +++ b/test/integration/nodebalancer_types_test.go @@ -0,0 +1,40 @@ +package integration + +import ( + "context" + "testing" + + "github.com/linode/linodego" + "github.com/stretchr/testify/require" +) + +func TestNodeBalancerType_List(t *testing.T) { + client, teardown := createTestClient(t, "fixtures/TestNodeBalancerType_List") + defer teardown() + + nbTypes, err := client.ListNodeBalancerTypes(context.Background(), nil) + require.NoError(t, err) + require.Greater(t, len(nbTypes), 0) + + for _, nbType := range nbTypes { + validateNodeBalancerType(t, nbType) + } +} + +func validateNodeBalancerType( + t *testing.T, + nbType linodego.NodeBalancerType, +) { + require.NotEmpty(t, nbType.ID) + require.NotEmpty(t, nbType.Label) + + require.Greater(t, nbType.Price.Hourly, 0.0) + require.Greater(t, nbType.Price.Monthly, 0.0) + require.GreaterOrEqual(t, nbType.Transfer, 0) + + for _, regionPrice := range nbType.RegionPrices { + require.NotEmpty(t, regionPrice.ID) + require.Greater(t, regionPrice.Hourly, 0.0) + require.Greater(t, regionPrice.Monthly, 0.0) + } +} diff --git a/test/integration/volume_types_test.go b/test/integration/volume_types_test.go new file mode 100644 index 000000000..919ac8080 --- /dev/null +++ b/test/integration/volume_types_test.go @@ -0,0 +1,40 @@ +package integration + +import ( + "context" + "testing" + + "github.com/linode/linodego" + "github.com/stretchr/testify/require" +) + +func TestVolumeType_List(t *testing.T) { + client, teardown := createTestClient(t, "fixtures/TestVolumeType_List") + defer teardown() + + volumeTypes, err := client.ListVolumeTypes(context.Background(), nil) + require.NoError(t, err) + require.Greater(t, len(volumeTypes), 0) + + for _, volumeType := range volumeTypes { + validateVolumeType(t, volumeType) + } +} + +func validateVolumeType( + t *testing.T, + volumeType linodego.VolumeType, +) { + require.NotEmpty(t, volumeType.ID) + require.NotEmpty(t, volumeType.Label) + + require.Greater(t, volumeType.Price.Hourly, 0.0) + require.Greater(t, volumeType.Price.Monthly, 0.0) + require.GreaterOrEqual(t, volumeType.Transfer, 0) + + for _, regionPrice := range volumeType.RegionPrices { + require.NotEmpty(t, regionPrice.ID) + require.Greater(t, regionPrice.Hourly, 0.0) + require.Greater(t, regionPrice.Monthly, 0.0) + } +} diff --git a/volumes_types.go b/volumes_types.go new file mode 100644 index 000000000..887a3c5aa --- /dev/null +++ b/volumes_types.go @@ -0,0 +1,45 @@ +package linodego + +import ( + "context" +) + +// VolumeType represents a single valid Volume type. +type VolumeType struct { + baseType[VolumeTypePrice, VolumeTypeRegionPrice] +} + +// VolumeTypePrice represents the base hourly and monthly prices +// for a volume type entry. +type VolumeTypePrice struct { + baseTypePrice +} + +// VolumeTypeRegionPrice represents the regional hourly and monthly prices +// for a volume type entry. +type VolumeTypeRegionPrice struct { + baseTypeRegionPrice +} + +// ListVolumeTypes lists Volume types. This endpoint is cached by default. +func (c *Client) ListVolumeTypes(ctx context.Context, opts *ListOptions) ([]VolumeType, error) { + e := "volumes/types" + + endpoint, err := generateListCacheURL(e, opts) + if err != nil { + return nil, err + } + + if result := c.getCachedResponse(endpoint); result != nil { + return result.([]VolumeType), nil + } + + response, err := getPaginatedResults[VolumeType](ctx, c, e, opts) + if err != nil { + return nil, err + } + + c.addCachedResponse(endpoint, response, &cacheExpiryTime) + + return response, nil +}