diff --git a/image_sharegroups_consumer.go b/image_sharegroups_consumer.go new file mode 100644 index 000000000..ff6f4997b --- /dev/null +++ b/image_sharegroups_consumer.go @@ -0,0 +1,208 @@ +package linodego + +import ( + "context" + "encoding/json" + "time" + + "github.com/linode/linodego/internal/parseabletime" +) + +// ConsumerImageShareGroup represents an ImageShareGroup that the consumer is a member of. +type ConsumerImageShareGroup struct { + ID int `json:"id"` + UUID string `json:"uuid"` + Label string `json:"label"` + Description string `json:"description"` + IsSuspended bool `json:"is_suspended"` + Created *time.Time `json:"-"` + Updated *time.Time `json:"-"` +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (isg *ConsumerImageShareGroup) UnmarshalJSON(b []byte) error { + type Mask ConsumerImageShareGroup + + p := struct { + *Mask + + Created *parseabletime.ParseableTime `json:"created"` + Updated *parseabletime.ParseableTime `json:"updated"` + }{ + Mask: (*Mask)(isg), + } + + if err := json.Unmarshal(b, &p); err != nil { + return err + } + + isg.Created = (*time.Time)(p.Created) + isg.Updated = (*time.Time)(p.Updated) + + return nil +} + +// ImageShareGroupToken contains information about a token created by a consumer. +// The token itself is only visible once upon creation. +type ImageShareGroupToken struct { + TokenUUID string `json:"token_uuid"` + Status string `json:"status"` + Label string `json:"label"` + ValidForShareGroupUUID string `json:"valid_for_sharegroup_uuid"` + Created *time.Time `json:"-"` + Updated *time.Time `json:"-"` + Expiry *time.Time `json:"-"` + ShareGroupUUID *string `json:"sharegroup_uuid"` + ShareGroupLabel *string `json:"sharegroup_label"` +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (t *ImageShareGroupToken) UnmarshalJSON(b []byte) error { + type Mask ImageShareGroupToken + + p := struct { + *Mask + + Created *parseabletime.ParseableTime `json:"created"` + Updated *parseabletime.ParseableTime `json:"updated"` + Expiry *parseabletime.ParseableTime `json:"expiry"` + }{ + Mask: (*Mask)(t), + } + + if err := json.Unmarshal(b, &p); err != nil { + return err + } + + t.Created = (*time.Time)(p.Created) + t.Updated = (*time.Time)(p.Updated) + t.Expiry = (*time.Time)(p.Expiry) + + return nil +} + +// ImageShareGroupCreateTokenResponse represents the response when the consumer +// creates a single-use ImageShareGroup membership token. +// The token itself is only provided upon creation, and must be given to the producer +// via an outside medium for the consumer to be added as a member of the producer's ImageShareGroup. +type ImageShareGroupCreateTokenResponse struct { + Token string `json:"token"` + TokenUUID string `json:"token_uuid"` + Status string `json:"status"` + Label string `json:"label"` + ValidForShareGroupUUID string `json:"valid_for_sharegroup_uuid"` + Created *time.Time `json:"-"` + Updated *time.Time `json:"-"` + Expiry *time.Time `json:"-"` + ShareGroupUUID *string `json:"sharegroup_uuid"` + ShareGroupLabel *string `json:"sharegroup_label"` +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (t *ImageShareGroupCreateTokenResponse) UnmarshalJSON(b []byte) error { + type Mask ImageShareGroupCreateTokenResponse + + p := struct { + *Mask + + Created *parseabletime.ParseableTime `json:"created"` + Updated *parseabletime.ParseableTime `json:"updated"` + Expiry *parseabletime.ParseableTime `json:"expiry"` + }{ + Mask: (*Mask)(t), + } + + if err := json.Unmarshal(b, &p); err != nil { + return err + } + + t.Created = (*time.Time)(p.Created) + t.Updated = (*time.Time)(p.Updated) + t.Expiry = (*time.Time)(p.Expiry) + + return nil +} + +// ImageShareGroupCreateTokenOptions fields are those accepted by ImageShareGroupCreateToken +type ImageShareGroupCreateTokenOptions struct { + Label *string `json:"label,omitempty"` + ValidForShareGroupUUID string `json:"valid_for_sharegroup_uuid"` +} + +// ImageShareGroupUpdateTokenOptions fields are those accepted by ImageShareGroupUpdateToken +type ImageShareGroupUpdateTokenOptions struct { + Label string `json:"label"` +} + +// ImageShareGroupListTokens lists information about all the ImageShareGroupTokens created by the user. +// The tokens themselves are only visible once upon creation. +func (c *Client) ImageShareGroupListTokens(ctx context.Context, opts *ListOptions) ([]ImageShareGroupToken, error) { + return getPaginatedResults[ImageShareGroupToken]( + ctx, + c, + "/images/sharegroups/tokens", + opts, + ) +} + +// ImageShareGroupGetToken gets information about the specified ImageShareGroupToken created by the user. +// the tokens themselves are only visible once upon creation. +func (c *Client) ImageShareGroupGetToken(ctx context.Context, tokenUUID string) (*ImageShareGroupToken, error) { + return doGETRequest[ImageShareGroupToken]( + ctx, + c, + formatAPIPath("images/sharegroups/tokens/%s", tokenUUID), + ) +} + +// ImageShareGroupCreateToken allows the consumer to create a single-use ImageShareGroup membership +// token for a specific ImageShareGroup owned by the producer. +func (c *Client) ImageShareGroupCreateToken(ctx context.Context, opts ImageShareGroupCreateTokenOptions) (*ImageShareGroupCreateTokenResponse, error) { + return doPOSTRequest[ImageShareGroupCreateTokenResponse]( + ctx, + c, + formatAPIPath("images/sharegroups/tokens"), + opts, + ) +} + +// ImageShareGroupUpdateToken allows the consumer to update an ImageShareGroupToken's label. +func (c *Client) ImageShareGroupUpdateToken(ctx context.Context, tokenUUID string, opts ImageShareGroupUpdateTokenOptions) (*ImageShareGroupToken, error) { + return doPUTRequest[ImageShareGroupToken]( + ctx, + c, + formatAPIPath("images/sharegroups/tokens/%s", tokenUUID), + opts, + ) +} + +// ImageShareGroupRemoveToken allows the consumer to remove an individual ImageShareGroupToken from an ImageShareGroup +// this token has been accepted into. +func (c *Client) ImageShareGroupRemoveToken(ctx context.Context, tokenUUID string) error { + return doDELETERequest( + ctx, + c, + formatAPIPath("images/sharegroups/tokens/%s", tokenUUID), + ) +} + +// ImageShareGroupGetByToken gets information about the ImageShareGroup that the +// consumer's specified token has been accepted into. +func (c *Client) ImageShareGroupGetByToken(ctx context.Context, tokenUUID string) (*ConsumerImageShareGroup, error) { + return doGETRequest[ConsumerImageShareGroup]( + ctx, + c, + formatAPIPath("images/sharegroups/tokens/%s/sharegroup", tokenUUID), + ) +} + +// ImageShareGroupGetImagesByToken lists the images in the ImageShareGroup that the +// consumer's specified token has been accepted into. +func (c *Client) ImageShareGroupGetImagesByToken(ctx context.Context, tokenUUID string, opts *ListOptions) ([]Image, error) { + return getPaginatedResults[Image]( + ctx, + c, + formatAPIPath("images/sharegroups/tokens/%s/sharegroup/images", tokenUUID), + opts, + ) +} diff --git a/image_sharegroups_producer.go b/image_sharegroups_producer.go new file mode 100644 index 000000000..31bbb5b81 --- /dev/null +++ b/image_sharegroups_producer.go @@ -0,0 +1,335 @@ +package linodego + +import ( + "context" + "encoding/json" + "time" + + "github.com/linode/linodego/internal/parseabletime" +) + +// ProducerImageShareGroup represents an ImageShareGroup owned by the producer. +type ProducerImageShareGroup struct { + ID int `json:"id"` + UUID string `json:"uuid"` + Label string `json:"label"` + Description string `json:"description"` + IsSuspended bool `json:"is_suspended"` + ImagesCount int `json:"images_count"` + MembersCount int `json:"members_count"` + Created *time.Time `json:"-"` + Updated *time.Time `json:"-"` + Expiry *time.Time `json:"-"` +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (isg *ProducerImageShareGroup) UnmarshalJSON(b []byte) error { + type Mask ProducerImageShareGroup + + p := struct { + *Mask + + Created *parseabletime.ParseableTime `json:"created"` + Updated *parseabletime.ParseableTime `json:"updated"` + Expiry *parseabletime.ParseableTime `json:"expiry"` + }{ + Mask: (*Mask)(isg), + } + + if err := json.Unmarshal(b, &p); err != nil { + return err + } + + isg.Created = (*time.Time)(p.Created) + isg.Updated = (*time.Time)(p.Updated) + isg.Expiry = (*time.Time)(p.Expiry) + + return nil +} + +// ImageShareGroupCreateOptions fields are those accepted by CreateImageShareGroup. +type ImageShareGroupCreateOptions struct { + Label string `json:"label"` + Description *string `json:"description,omitempty"` + Images []ImageShareGroupImage `json:"images,omitempty"` +} + +// ImageShareGroupUpdateOptions fields are those accepted by UpdateImageShareGroup. +type ImageShareGroupUpdateOptions struct { + Label *string `json:"label,omitempty"` + Description *string `json:"description,omitempty"` +} + +// ImageShareGroupAddImagesOptions fields are those accepted by ImageShareGroupAddImages. +type ImageShareGroupAddImagesOptions struct { + Images []ImageShareGroupImage `json:"images"` +} + +// ImageShareGroupUpdateImageOptions fields are those accepted by ImageShareGroupUpdateImage. +type ImageShareGroupUpdateImageOptions struct { + Label *string `json:"label,omitempty"` + Description *string `json:"description,omitempty"` +} + +// ImageShareGroupImage represents an Image to be included in a ProducerImageShareGroup. +type ImageShareGroupImage struct { + ImageID string `json:"image_id"` + Label *string `json:"label,omitempty"` + Description *string `json:"description,omitempty"` +} + +// ImageShareGroupMember represents a Member of an ImageShareGroup owned by the producer. +type ImageShareGroupMember struct { + TokenUUID string `json:"token_uuid"` + Status string `json:"status"` + Label string `json:"label"` + Created *time.Time `json:"-"` + Updated *time.Time `json:"-"` + Expiry *time.Time `json:"-"` +} + +// ImageShareGroupUpdateMemberOptions fields are those accepted by ImageShareGroupUpdateMember. +type ImageShareGroupUpdateMemberOptions struct { + Label string `json:"label"` +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (m *ImageShareGroupMember) UnmarshalJSON(b []byte) error { + type Mask ImageShareGroupMember + + p := struct { + *Mask + + Created *parseabletime.ParseableTime `json:"created"` + Updated *parseabletime.ParseableTime `json:"updated"` + Expiry *parseabletime.ParseableTime `json:"expiry"` + }{ + Mask: (*Mask)(m), + } + + if err := json.Unmarshal(b, &p); err != nil { + return err + } + + m.Created = (*time.Time)(p.Created) + m.Updated = (*time.Time)(p.Updated) + m.Expiry = (*time.Time)(p.Expiry) + + return nil +} + +// ImageShareGroupAddMemberOptions fields are those accepted by ImageShareGroupAddMember. +// The token must be provided to the producer by the consumer via an outside medium. +type ImageShareGroupAddMemberOptions struct { + Token string `json:"token"` + Label string `json:"label"` +} + +// ListImageShareGroups lists all ImageShareGroups owned by the producer. +func (c *Client) ListImageShareGroups( + ctx context.Context, + opts *ListOptions, +) ([]ProducerImageShareGroup, error) { + return getPaginatedResults[ProducerImageShareGroup]( + ctx, + c, + "images/sharegroups", + opts, + ) +} + +// ListImageShareGroupsContainingPrivateImage lists all current ImageShareGroups owned by the producer where +// the given private image is present. +func (c *Client) ListImageShareGroupsContainingPrivateImage( + ctx context.Context, + privateImageID int, + opts *ListOptions, +) ([]ProducerImageShareGroup, error) { + return getPaginatedResults[ProducerImageShareGroup]( + ctx, + c, + formatAPIPath("images/%d/sharegroups", privateImageID), + opts, + ) +} + +// GetImageShareGroup gets the specified ImageShareGroup owned by the producer. +func (c *Client) GetImageShareGroup( + ctx context.Context, + imageShareGroupID int, +) (*ProducerImageShareGroup, error) { + return doGETRequest[ProducerImageShareGroup]( + ctx, + c, + formatAPIPath("images/sharegroups/%d", imageShareGroupID), + ) +} + +// CreateImageShareGroup allows the producer to create a new ImageShareGroup. +func (c *Client) CreateImageShareGroup( + ctx context.Context, + opts ImageShareGroupCreateOptions, +) (*ProducerImageShareGroup, error) { + return doPOSTRequest[ProducerImageShareGroup]( + ctx, + c, + "images/sharegroups", + opts, + ) +} + +// UpdateImageShareGroup allows the producer to update an existing ImageShareGroup's description and label. +func (c *Client) UpdateImageShareGroup( + ctx context.Context, + imageShareGroupID int, + opts ImageShareGroupUpdateOptions, +) (*ProducerImageShareGroup, error) { + return doPUTRequest[ProducerImageShareGroup]( + ctx, + c, + formatAPIPath("images/sharegroups/%d", imageShareGroupID), + opts, + ) +} + +// DeleteImageShareGroup deletes the specified ImageShareGroup owned by the producer. +func (c *Client) DeleteImageShareGroup(ctx context.Context, imageShareGroupID int) error { + return doDELETERequest( + ctx, + c, + formatAPIPath("images/sharegroups/%d", imageShareGroupID), + ) +} + +// ImageShareGroupListImages lists the Images in a specified ImageShareGroup owned by the producer. +func (c *Client) ImageShareGroupListImages( + ctx context.Context, + imageShareGroupID int, + opts *ListOptions, +) ([]Image, error) { + return getPaginatedResults[Image]( + ctx, + c, + formatAPIPath("images/sharegroups/%d/images", imageShareGroupID), + opts, + ) +} + +// ImageShareGroupAddImages allows the producer to add images to a specific ImageShareGroup. +func (c *Client) ImageShareGroupAddImages( + ctx context.Context, + imageShareGroupID int, + opts ImageShareGroupAddImagesOptions, +) ([]Image, error) { + response, err := doPOSTRequest[[]Image]( + ctx, + c, + formatAPIPath("images/sharegroups/%d/images", imageShareGroupID), + opts, + ) + if err != nil { + return nil, err + } + + return *response, nil +} + +// ImageShareGroupUpdateImage allows the producer to update the specified Image's description and label within an ImageShareGroup. +func (c *Client) ImageShareGroupUpdateImage( + ctx context.Context, + imageShareGroupID int, + imageID string, + opts ImageShareGroupUpdateImageOptions, +) (*Image, error) { + return doPUTRequest[Image]( + ctx, + c, + formatAPIPath("images/sharegroups/%d/images/%s", imageShareGroupID, imageID), + opts, + ) +} + +// ImageShareGroupRemoveImage allows the producer to remove access to an image within an ImageShareGroup owned by the producer. +func (c *Client) ImageShareGroupRemoveImage( + ctx context.Context, + imageShareGroupID int, + imageID string, +) error { + return doDELETERequest( + ctx, + c, + formatAPIPath("images/sharegroups/%d/images/%s", imageShareGroupID, imageID), + ) +} + +// ImageShareGroupListMembers lists the ImageShareGroupMembers of the provided ImageShareGroup owned by the producer. +func (c *Client) ImageShareGroupListMembers( + ctx context.Context, + imageShareGroupID int, + opts *ListOptions, +) ([]ImageShareGroupMember, error) { + return getPaginatedResults[ImageShareGroupMember]( + ctx, + c, + formatAPIPath("images/sharegroups/%d/members", imageShareGroupID), + opts, + ) +} + +// ImageShareGroupGetMember gets the details of the specified ImageShareGroupMember in the specified +// ImageShareGroup owned by the producer. +func (c *Client) ImageShareGroupGetMember( + ctx context.Context, + imageShareGroupID int, + tokenUUID string, +) (*ImageShareGroupMember, error) { + return doGETRequest[ImageShareGroupMember]( + ctx, + c, + formatAPIPath("images/sharegroups/%d/members/%s", imageShareGroupID, tokenUUID), + ) +} + +// ImageShareGroupAddMember allows the producer to add members to a specific ImageShareGroup. +func (c *Client) ImageShareGroupAddMember( + ctx context.Context, + imageShareGroupID int, + opts ImageShareGroupAddMemberOptions, +) (*ImageShareGroupMember, error) { + return doPOSTRequest[ImageShareGroupMember]( + ctx, + c, + formatAPIPath("images/sharegroups/%d/members", imageShareGroupID), + opts, + ) +} + +// ImageShareGroupUpdateMember allows the producer to update the label associated with the specified +// ImageShareGroupMember in the specified ImageShareGroup owned by the producer. +func (c *Client) ImageShareGroupUpdateMember( + ctx context.Context, + imageShareGroupID int, + tokenUUID string, + opts ImageShareGroupUpdateMemberOptions, +) (*ImageShareGroupMember, error) { + return doPUTRequest[ImageShareGroupMember]( + ctx, + c, + formatAPIPath("images/sharegroups/%d/members/%s", imageShareGroupID, tokenUUID), + opts, + ) +} + +// ImageShareGroupRemoveMember allows the producer to remove an individual ImageShareGroupMember +// that’s been accepted into the ImageShareGroup owned by the producer. +func (c *Client) ImageShareGroupRemoveMember( + ctx context.Context, + imageShareGroupID int, + tokenUUID string, +) error { + return doDELETERequest( + ctx, + c, + formatAPIPath("images/sharegroups/%d/members/%s", imageShareGroupID, tokenUUID), + ) +} diff --git a/images.go b/images.go index 858136a41..b2a045db1 100644 --- a/images.go +++ b/images.go @@ -43,16 +43,17 @@ type ImageRegion struct { // Image represents a deployable Image object for use with Linode Instances type Image struct { ID string `json:"id"` - CreatedBy string `json:"created_by"` + CreatedBy *string `json:"created_by"` Capabilities []string `json:"capabilities"` Label string `json:"label"` Description string `json:"description"` Type string `json:"type"` - Vendor string `json:"vendor"` + Vendor *string `json:"vendor"` Status ImageStatus `json:"status"` Size int `json:"size"` TotalSize int `json:"total_size"` IsPublic bool `json:"is_public"` + IsShared *bool `json:"is_shared"` Deprecated bool `json:"deprecated"` Regions []ImageRegion `json:"regions"` Tags []string `json:"tags"` @@ -61,6 +62,25 @@ type Image struct { Created *time.Time `json:"-"` Expiry *time.Time `json:"-"` EOL *time.Time `json:"-"` + + ImageSharing ImageSharing `json:"image_sharing"` +} + +type ImageSharing struct { + SharedWith *ImageSharingSharedWith `json:"shared_with"` + SharedBy *ImageSharingSharedBy `json:"shared_by"` +} + +type ImageSharingSharedWith struct { + ShareGroupCount int `json:"sharegroup_count"` + ImageShareGroupListURL string `json:"image_sharegroup_list_url"` +} + +type ImageSharingSharedBy struct { + ShareGroupID int `json:"sharegroup_id"` + ShareGroupUUID string `json:"sharegroup_uuid"` + ShareGroupLabel string `json:"sharegroup_label"` + SourceImageID *string `json:"source_image_id"` } // ImageCreateOptions fields are those accepted by CreateImage diff --git a/test/integration/fixtures/TestInstance_GetMonthlyTransfer.yaml b/test/integration/fixtures/TestInstance_GetMonthlyTransfer.yaml index da6a975be..fda5cf9b6 100644 --- a/test/integration/fixtures/TestInstance_GetMonthlyTransfer.yaml +++ b/test/integration/fixtures/TestInstance_GetMonthlyTransfer.yaml @@ -14,115 +14,119 @@ interactions: url: https://api.linode.com/v4beta/regions?page=1 method: GET response: - body: '{"data": [{"id": "ap-west", "label": "Mumbai, IN", "country": "in", "capabilities": - ["Linodes", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", - "Block Storage", "GPU Linodes", "Kubernetes", "Cloud Firewall", "Vlans", "Block - Storage Migrations", "Managed Databases", "Metadata", "Placement Group", "StackScripts"], + body: '{"data": [{"id": "nl-ams", "label": "Amsterdam, NL", "country": "nl", "capabilities": + ["Linodes", "Block Storage Encryption", "LA Disk Encryption", "Disk Encryption", + "Backups", "NodeBalancers", "Block Storage", "Object Storage", "Kubernetes", + "Kubernetes Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", + "Metadata", "Premium Plans", "Placement Group", "StackScripts", "Linode Interfaces"], "monitors": {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, - "status": "ok", "resolvers": {"ipv4": "172.105.34.5,172.105.35.5,172.105.36.5,172.105.37.5,172.105.38.5,172.105.39.5,172.105.40.5,172.105.41.5,172.105.42.5,172.105.43.5", + "status": "ok", "resolvers": {"ipv4": "172.233.33.36,172.233.33.38,172.233.33.35,172.233.33.39,172.233.33.34,172.233.33.33,172.233.33.31,172.233.33.30,172.233.33.37,172.233.33.32", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "ca-central", - "label": "Toronto, CA", "country": "ca", "capabilities": ["Linodes", "LA Disk + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "no-osl-1", + "label": "Oslo, NO", "country": "no", "capabilities": ["Linodes", "Block Storage Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", - "Kubernetes", "Cloud Firewall", "Vlans", "Block Storage Migrations", "Managed - Databases", "Metadata", "Placement Group", "StackScripts"], "monitors": {"alerts": - ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": - {"ipv4": "172.105.0.5,172.105.3.5,172.105.4.5,172.105.5.5,172.105.6.5,172.105.7.5,172.105.8.5,172.105.9.5,172.105.10.5,172.105.11.5", + "Kubernetes", "Kubernetes Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed + Databases", "Metadata", "Premium Plans", "Placement Group", "StackScripts", + "Linode Interfaces"], "monitors": {"alerts": ["Managed Databases"], "metrics": + ["Managed Databases"]}, "status": "ok", "resolvers": {"ipv4": "172.238.140.33,172.238.140.25,172.238.140.30,172.238.140.31,172.238.140.26,172.238.140.28,172.238.140.34,172.238.140.32,172.238.140.27,172.238.140.29", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "ap-southeast", - "label": "Sydney, AU", "country": "au", "capabilities": ["Linodes", "LA Disk - Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", - "Kubernetes", "Cloud Firewall", "Vlans", "Block Storage Migrations", "Managed - Databases", "Metadata", "Placement Group", "StackScripts"], "monitors": {"alerts": - ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": - {"ipv4": "172.105.166.5,172.105.169.5,172.105.168.5,172.105.172.5,172.105.162.5,172.105.170.5,172.105.167.5,172.105.171.5,172.105.181.5,172.105.161.5", - "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, - "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "us-iad", - "label": "Washington, DC", "country": "us", "capabilities": ["Linodes", "Block + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "jp-tyo-3", + "label": "Tokyo 3, JP", "country": "jp", "capabilities": ["Linodes", "Block Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", "Object Storage", "Kubernetes", "Kubernetes Enterprise", "Cloud - Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", "Premium Plans", - "Placement Group", "StackScripts"], "monitors": {"alerts": ["Managed Databases"], - "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": {"ipv4": "139.144.192.62,139.144.192.60,139.144.192.61,139.144.192.53,139.144.192.54,139.144.192.67,139.144.192.69,139.144.192.66,139.144.192.52,139.144.192.68", + Firewall", "Vlans", "VPCs", "Metadata", "Premium Plans", "Placement Group", + "StackScripts", "Linode Interfaces"], "monitors": {"alerts": [], "metrics": + []}, "status": "ok", "resolvers": {"ipv4": "172.237.4.15,172.237.4.19,172.237.4.17,172.237.4.21,172.237.4.16,172.237.4.18,172.237.4.23,172.237.4.24,172.237.4.20,172.237.4.14", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "us-ord", - "label": "Chicago, IL", "country": "us", "capabilities": ["Linodes", "Block + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "sg-sin-2", + "label": "Singapore 2, SG", "country": "sg", "capabilities": ["Linodes", "Block Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", "Object Storage", "GPU Linodes", "Kubernetes", "Kubernetes Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", - "Premium Plans", "Placement Group", "StackScripts"], "monitors": {"alerts": - ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": - {"ipv4": "172.232.0.17,172.232.0.16,172.232.0.21,172.232.0.13,172.232.0.22,172.232.0.9,172.232.0.19,172.232.0.20,172.232.0.15,172.232.0.18", + "Premium Plans", "Placement Group", "StackScripts", "Linode Interfaces"], "monitors": + {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": + "ok", "resolvers": {"ipv4": "172.236.129.8,172.236.129.42,172.236.129.41,172.236.129.19,172.236.129.46,172.236.129.23,172.236.129.48,172.236.129.20,172.236.129.21,172.236.129.47", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "fr-par", - "label": "Paris, FR", "country": "fr", "capabilities": ["Linodes", "Block Storage - Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", - "Block Storage", "Object Storage", "GPU Linodes", "Kubernetes", "Kubernetes - Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", - "Premium Plans", "Placement Group", "StackScripts"], "monitors": {"alerts": - ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": - {"ipv4": "172.232.32.21,172.232.32.23,172.232.32.17,172.232.32.18,172.232.32.16,172.232.32.22,172.232.32.20,172.232.32.14,172.232.32.11,172.232.32.12", + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "de-fra-2", + "label": "Frankfurt 2, DE", "country": "de", "capabilities": ["Linodes", "Block + Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", + "Block Storage", "GPU Linodes", "Kubernetes", "Kubernetes Enterprise", "Cloud + Firewall", "Vlans", "VPCs", "Metadata", "Premium Plans", "Placement Group", + "StackScripts", "NETINT Quadra T1U", "Linode Interfaces"], "monitors": {"alerts": + [], "metrics": []}, "status": "ok", "resolvers": {"ipv4": "172.236.203.9,172.236.203.16,172.236.203.19,172.236.203.15,172.236.203.17,172.236.203.11,172.236.203.18,172.236.203.14,172.236.203.13,172.236.203.12", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "us-sea", - "label": "Seattle, WA", "country": "us", "capabilities": ["Linodes", "Block + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "in-bom-2", + "label": "Mumbai 2, IN", "country": "in", "capabilities": ["Linodes", "Block Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", - "Block Storage", "Object Storage", "GPU Linodes", "Kubernetes", "Kubernetes - Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", - "Premium Plans", "Placement Group", "StackScripts"], "monitors": {"alerts": + "Block Storage", "GPU Linodes", "Kubernetes", "Kubernetes Enterprise", "Cloud + Firewall", "Vlans", "VPCs", "Metadata", "Premium Plans", "Placement Group", + "StackScripts", "Linode Interfaces"], "monitors": {"alerts": [], "metrics": + []}, "status": "ok", "resolvers": {"ipv4": "172.236.171.41,172.236.171.42,172.236.171.25,172.236.171.44,172.236.171.26,172.236.171.45,172.236.171.24,172.236.171.43,172.236.171.27,172.236.171.28", + "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, + "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "au-mel", + "label": "Melbourne, AU", "country": "au", "capabilities": ["Linodes", "Block + Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", + "Block Storage", "Object Storage", "Kubernetes", "Cloud Firewall", "Vlans", + "VPCs", "Managed Databases", "Metadata", "Premium Plans", "Placement Group", + "StackScripts", "NETINT Quadra T1U", "Linode Interfaces"], "monitors": {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": - {"ipv4": "172.232.160.19,172.232.160.21,172.232.160.17,172.232.160.15,172.232.160.18,172.232.160.8,172.232.160.12,172.232.160.11,172.232.160.14,172.232.160.16", + {"ipv4": "172.236.32.23,172.236.32.35,172.236.32.30,172.236.32.28,172.236.32.32,172.236.32.33,172.236.32.27,172.236.32.37,172.236.32.29,172.236.32.34", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "br-gru", - "label": "Sao Paulo, BR", "country": "br", "capabilities": ["Linodes", "Block + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "gb-lon", + "label": "London 2, UK", "country": "gb", "capabilities": ["Linodes", "Block Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", "Object Storage", "Kubernetes", "Kubernetes Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", "Premium Plans", - "Placement Group", "StackScripts"], "monitors": {"alerts": ["Managed Databases"], - "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": {"ipv4": "172.233.0.4,172.233.0.9,172.233.0.7,172.233.0.12,172.233.0.5,172.233.0.13,172.233.0.10,172.233.0.6,172.233.0.8,172.233.0.11", + "Placement Group", "StackScripts", "Maintenance Policy", "Linode Interfaces"], + "monitors": {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, + "status": "ok", "resolvers": {"ipv4": "172.236.0.46,172.236.0.50,172.236.0.47,172.236.0.53,172.236.0.52,172.236.0.45,172.236.0.49,172.236.0.51,172.236.0.54,172.236.0.48", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "nl-ams", - "label": "Amsterdam, NL", "country": "nl", "capabilities": ["Linodes", "Block + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "us-lax", + "label": "Los Angeles, CA", "country": "us", "capabilities": ["Linodes", "Block Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", "Object Storage", "Kubernetes", "Kubernetes Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", "Premium Plans", - "Placement Group", "StackScripts"], "monitors": {"alerts": ["Managed Databases"], - "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": {"ipv4": "172.233.33.36,172.233.33.38,172.233.33.35,172.233.33.39,172.233.33.34,172.233.33.33,172.233.33.31,172.233.33.30,172.233.33.37,172.233.33.32", + "Placement Group", "StackScripts", "NETINT Quadra T1U", "Maintenance Policy", + "Linode Interfaces"], "monitors": {"alerts": ["Managed Databases"], "metrics": + ["Managed Databases"]}, "status": "ok", "resolvers": {"ipv4": "172.233.128.45,172.233.128.38,172.233.128.53,172.233.128.37,172.233.128.34,172.233.128.36,172.233.128.33,172.233.128.39,172.233.128.43,172.233.128.44", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "se-sto", - "label": "Stockholm, SE", "country": "se", "capabilities": ["Linodes", "Block + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "id-cgk", + "label": "Jakarta, ID", "country": "id", "capabilities": ["Linodes", "Block Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", "Object Storage", "Kubernetes", "Kubernetes Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", "Premium Plans", - "Placement Group", "StackScripts"], "monitors": {"alerts": ["Managed Databases"], - "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": {"ipv4": "172.232.128.24,172.232.128.26,172.232.128.20,172.232.128.22,172.232.128.25,172.232.128.19,172.232.128.23,172.232.128.18,172.232.128.21,172.232.128.27", + "Placement Group", "StackScripts", "Linode Interfaces"], "monitors": {"alerts": + ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": + {"ipv4": "172.232.224.23,172.232.224.32,172.232.224.26,172.232.224.27,172.232.224.21,172.232.224.24,172.232.224.22,172.232.224.20,172.232.224.31,172.232.224.28", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "es-mad", - "label": "Madrid, ES", "country": "es", "capabilities": ["Linodes", "Block Storage + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "us-mia", + "label": "Miami, FL", "country": "us", "capabilities": ["Linodes", "Block Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", "Object Storage", "Kubernetes", "Kubernetes Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", "Premium Plans", - "Placement Group", "StackScripts"], "monitors": {"alerts": ["Managed Databases"], - "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": {"ipv4": "172.233.111.6,172.233.111.17,172.233.111.21,172.233.111.25,172.233.111.19,172.233.111.12,172.233.111.26,172.233.111.16,172.233.111.18,172.233.111.9", + "Placement Group", "StackScripts", "NETINT Quadra T1U", "Linode Interfaces"], + "monitors": {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, + "status": "ok", "resolvers": {"ipv4": "172.233.160.34,172.233.160.27,172.233.160.30,172.233.160.29,172.233.160.32,172.233.160.28,172.233.160.33,172.233.160.26,172.233.160.25,172.233.160.31", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "in-maa", - "label": "Chennai, IN", "country": "in", "capabilities": ["Linodes", "Block - Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "it-mil", + "label": "Milan, IT", "country": "it", "capabilities": ["Linodes", "Block Storage + Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", "Object Storage", "Kubernetes", "Kubernetes Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", "Premium Plans", - "Placement Group", "StackScripts", "NETINT Quadra T1U"], "monitors": {"alerts": + "Placement Group", "StackScripts", "Linode Interfaces"], "monitors": {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": - {"ipv4": "172.232.96.17,172.232.96.26,172.232.96.19,172.232.96.20,172.232.96.25,172.232.96.21,172.232.96.18,172.232.96.22,172.232.96.23,172.232.96.24", + {"ipv4": "172.232.192.19,172.232.192.18,172.232.192.16,172.232.192.20,172.232.192.24,172.232.192.21,172.232.192.22,172.232.192.17,172.232.192.15,172.232.192.23", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "jp-osa", @@ -130,255 +134,198 @@ interactions: Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", "Object Storage", "GPU Linodes", "Kubernetes", "Kubernetes Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", - "Premium Plans", "Placement Group", "StackScripts"], "monitors": {"alerts": - ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": - {"ipv4": "172.233.64.44,172.233.64.43,172.233.64.37,172.233.64.40,172.233.64.46,172.233.64.41,172.233.64.39,172.233.64.42,172.233.64.45,172.233.64.38", + "Premium Plans", "Placement Group", "StackScripts", "Linode Interfaces"], "monitors": + {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": + "ok", "resolvers": {"ipv4": "172.233.64.44,172.233.64.43,172.233.64.37,172.233.64.40,172.233.64.46,172.233.64.41,172.233.64.39,172.233.64.42,172.233.64.45,172.233.64.38", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "it-mil", - "label": "Milan, IT", "country": "it", "capabilities": ["Linodes", "Block Storage - Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "in-maa", + "label": "Chennai, IN", "country": "in", "capabilities": ["Linodes", "Block + Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", "Object Storage", "Kubernetes", "Kubernetes Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", "Premium Plans", - "Placement Group", "StackScripts"], "monitors": {"alerts": ["Managed Databases"], - "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": {"ipv4": "172.232.192.19,172.232.192.18,172.232.192.16,172.232.192.20,172.232.192.24,172.232.192.21,172.232.192.22,172.232.192.17,172.232.192.15,172.232.192.23", + "Placement Group", "StackScripts", "NETINT Quadra T1U", "Linode Interfaces"], + "monitors": {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, + "status": "ok", "resolvers": {"ipv4": "172.232.96.17,172.232.96.26,172.232.96.19,172.232.96.20,172.232.96.25,172.232.96.21,172.232.96.18,172.232.96.22,172.232.96.23,172.232.96.24", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "us-mia", - "label": "Miami, FL", "country": "us", "capabilities": ["Linodes", "Block Storage + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "es-mad", + "label": "Madrid, ES", "country": "es", "capabilities": ["Linodes", "Block Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", "Object Storage", "Kubernetes", "Kubernetes Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", "Premium Plans", - "Placement Group", "StackScripts", "NETINT Quadra T1U"], "monitors": {"alerts": + "Placement Group", "StackScripts", "Linode Interfaces"], "monitors": {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": - {"ipv4": "172.233.160.34,172.233.160.27,172.233.160.30,172.233.160.29,172.233.160.32,172.233.160.28,172.233.160.33,172.233.160.26,172.233.160.25,172.233.160.31", + {"ipv4": "172.233.111.6,172.233.111.17,172.233.111.21,172.233.111.25,172.233.111.19,172.233.111.12,172.233.111.26,172.233.111.16,172.233.111.18,172.233.111.9", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "id-cgk", - "label": "Jakarta, ID", "country": "id", "capabilities": ["Linodes", "Block + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "se-sto", + "label": "Stockholm, SE", "country": "se", "capabilities": ["Linodes", "Block Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", "Object Storage", "Kubernetes", "Kubernetes Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", "Premium Plans", - "Placement Group", "StackScripts"], "monitors": {"alerts": ["Managed Databases"], - "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": {"ipv4": "172.232.224.23,172.232.224.32,172.232.224.26,172.232.224.27,172.232.224.21,172.232.224.24,172.232.224.22,172.232.224.20,172.232.224.31,172.232.224.28", + "Placement Group", "StackScripts", "Linode Interfaces"], "monitors": {"alerts": + ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": + {"ipv4": "172.232.128.24,172.232.128.26,172.232.128.20,172.232.128.22,172.232.128.25,172.232.128.19,172.232.128.23,172.232.128.18,172.232.128.21,172.232.128.27", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "us-lax", - "label": "Los Angeles, CA", "country": "us", "capabilities": ["Linodes", "Block + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "br-gru", + "label": "Sao Paulo, BR", "country": "br", "capabilities": ["Linodes", "Block Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", "Object Storage", "Kubernetes", "Kubernetes Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", "Premium Plans", - "Placement Group", "StackScripts", "NETINT Quadra T1U"], "monitors": {"alerts": + "Placement Group", "StackScripts", "Linode Interfaces"], "monitors": {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": - {"ipv4": "172.233.128.45,172.233.128.38,172.233.128.53,172.233.128.37,172.233.128.34,172.233.128.36,172.233.128.33,172.233.128.39,172.233.128.43,172.233.128.44", + {"ipv4": "172.233.0.4,172.233.0.9,172.233.0.7,172.233.0.12,172.233.0.5,172.233.0.13,172.233.0.10,172.233.0.6,172.233.0.8,172.233.0.11", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "nz-akl-1", - "label": "Auckland, NZ", "country": "nz", "capabilities": ["Linodes", "Disk - Encryption", "Cloud Firewall", "Vlans", "VPCs", "Metadata", "Distributed Plans"], - "monitors": {"alerts": [], "metrics": []}, "status": "ok", "resolvers": {"ipv4": - "173.223.100.53,173.223.101.53", "ipv6": "1234::5678,1234::5678"}, - "placement_group_limits": {"maximum_pgs_per_customer": 0, "maximum_linodes_per_pg": - 0, "maximum_linodes_per_flexible_pg": 0}, "site_type": "distributed"}, {"id": - "us-den-1", "label": "Denver, CO", "country": "us", "capabilities": ["Linodes", - "Disk Encryption", "Cloud Firewall", "Vlans", "VPCs", "Metadata", "Distributed - Plans"], "monitors": {"alerts": [], "metrics": []}, "status": "ok", "resolvers": - {"ipv4": "173.223.100.53,173.223.101.53", "ipv6": "1234::5678,1234::5678"}, - "placement_group_limits": {"maximum_pgs_per_customer": 0, "maximum_linodes_per_pg": - 0, "maximum_linodes_per_flexible_pg": 0}, "site_type": "distributed"}, {"id": - "de-ham-1", "label": "Hamburg, DE", "country": "de", "capabilities": ["Linodes", - "Disk Encryption", "Cloud Firewall", "Vlans", "VPCs", "Metadata", "Distributed - Plans"], "monitors": {"alerts": [], "metrics": []}, "status": "ok", "resolvers": - {"ipv4": "173.223.100.53,173.223.101.53", "ipv6": "1234::5678,1234::5678"}, - "placement_group_limits": {"maximum_pgs_per_customer": 0, "maximum_linodes_per_pg": - 0, "maximum_linodes_per_flexible_pg": 0}, "site_type": "distributed"}, {"id": - "fr-mrs-1", "label": "Marseille, FR", "country": "fr", "capabilities": ["Linodes", - "Disk Encryption", "Cloud Firewall", "Vlans", "VPCs", "Metadata", "Distributed - Plans"], "monitors": {"alerts": [], "metrics": []}, "status": "ok", "resolvers": - {"ipv4": "173.223.100.53,173.223.101.53", "ipv6": "1234::5678,1234::5678"}, - "placement_group_limits": {"maximum_pgs_per_customer": 0, "maximum_linodes_per_pg": - 0, "maximum_linodes_per_flexible_pg": 0}, "site_type": "distributed"}, {"id": - "za-jnb-1", "label": "Johannesburg, ZA", "country": "za", "capabilities": ["Linodes", - "Disk Encryption", "Cloud Firewall", "Vlans", "VPCs", "Metadata", "Distributed - Plans"], "monitors": {"alerts": [], "metrics": []}, "status": "ok", "resolvers": - {"ipv4": "173.223.100.53,173.223.101.53", "ipv6": "1234::5678,1234::5678"}, - "placement_group_limits": {"maximum_pgs_per_customer": 0, "maximum_linodes_per_pg": - 0, "maximum_linodes_per_flexible_pg": 0}, "site_type": "distributed"}, {"id": - "my-kul-1", "label": "Kuala Lumpur, MY", "country": "my", "capabilities": ["Linodes", - "Disk Encryption", "Cloud Firewall", "Vlans", "VPCs", "Metadata", "Distributed - Plans"], "monitors": {"alerts": [], "metrics": []}, "status": "ok", "resolvers": - {"ipv4": "173.223.100.53,173.223.101.53", "ipv6": "1234::5678,1234::5678"}, - "placement_group_limits": {"maximum_pgs_per_customer": 0, "maximum_linodes_per_pg": - 0, "maximum_linodes_per_flexible_pg": 0}, "site_type": "distributed"}, {"id": - "co-bog-1", "label": "Bogot\u00e1, CO", "country": "co", "capabilities": ["Linodes", - "Disk Encryption", "Cloud Firewall", "Vlans", "VPCs", "Metadata", "Distributed - Plans"], "monitors": {"alerts": [], "metrics": []}, "status": "ok", "resolvers": - {"ipv4": "173.223.100.53,173.223.101.53", "ipv6": "1234::5678,1234::5678"}, - "placement_group_limits": {"maximum_pgs_per_customer": 0, "maximum_linodes_per_pg": - 0, "maximum_linodes_per_flexible_pg": 0}, "site_type": "distributed"}, {"id": - "mx-qro-1", "label": "Quer\u00e9taro, MX", "country": "mx", "capabilities": - ["Linodes", "Disk Encryption", "Cloud Firewall", "Vlans", "VPCs", "Metadata", - "Distributed Plans"], "monitors": {"alerts": [], "metrics": []}, "status": "ok", - "resolvers": {"ipv4": "173.223.100.53,173.223.101.53", "ipv6": "1234::5678,1234::5678"}, - "placement_group_limits": {"maximum_pgs_per_customer": 0, "maximum_linodes_per_pg": - 0, "maximum_linodes_per_flexible_pg": 0}, "site_type": "distributed"}, {"id": - "us-hou-1", "label": "Houston, TX", "country": "us", "capabilities": ["Linodes", - "Disk Encryption", "Cloud Firewall", "Vlans", "VPCs", "Metadata", "Distributed - Plans"], "monitors": {"alerts": [], "metrics": []}, "status": "ok", "resolvers": - {"ipv4": "173.223.100.53,173.223.101.53", "ipv6": "1234::5678,1234::5678"}, - "placement_group_limits": {"maximum_pgs_per_customer": 0, "maximum_linodes_per_pg": - 0, "maximum_linodes_per_flexible_pg": 0}, "site_type": "distributed"}, {"id": - "cl-scl-1", "label": "Santiago, CL", "country": "cl", "capabilities": ["Linodes", - "Disk Encryption", "Cloud Firewall", "Vlans", "VPCs", "Metadata", "Distributed - Plans"], "monitors": {"alerts": [], "metrics": []}, "status": "ok", "resolvers": - {"ipv4": "173.223.100.53,173.223.101.53", "ipv6": "1234::5678,1234::5678"}, - "placement_group_limits": {"maximum_pgs_per_customer": 0, "maximum_linodes_per_pg": - 0, "maximum_linodes_per_flexible_pg": 0}, "site_type": "distributed"}, {"id": - "gb-lon", "label": "London 2, UK", "country": "gb", "capabilities": ["Linodes", - "Block Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", - "NodeBalancers", "Block Storage", "Object Storage", "Kubernetes", "Kubernetes + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "us-sea", + "label": "Seattle, WA", "country": "us", "capabilities": ["Linodes", "Block + Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", + "Block Storage", "Object Storage", "GPU Linodes", "Kubernetes", "Kubernetes Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", - "Premium Plans", "Placement Group", "StackScripts"], "monitors": {"alerts": - ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": - {"ipv4": "172.236.0.46,172.236.0.50,172.236.0.47,172.236.0.53,172.236.0.52,172.236.0.45,172.236.0.49,172.236.0.51,172.236.0.54,172.236.0.48", + "Premium Plans", "Placement Group", "StackScripts", "Maintenance Policy", "Linode + Interfaces"], "monitors": {"alerts": ["Managed Databases"], "metrics": ["Managed + Databases"]}, "status": "ok", "resolvers": {"ipv4": "172.232.160.19,172.232.160.21,172.232.160.17,172.232.160.15,172.232.160.18,172.232.160.8,172.232.160.12,172.232.160.11,172.232.160.14,172.232.160.16", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "au-mel", - "label": "Melbourne, AU", "country": "au", "capabilities": ["Linodes", "Block - Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", - "Block Storage", "Object Storage", "Kubernetes", "Cloud Firewall", "Vlans", - "VPCs", "Managed Databases", "Metadata", "Premium Plans", "Placement Group", - "StackScripts", "NETINT Quadra T1U"], "monitors": {"alerts": ["Managed Databases"], - "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": {"ipv4": "172.236.32.23,172.236.32.35,172.236.32.30,172.236.32.28,172.236.32.32,172.236.32.33,172.236.32.27,172.236.32.37,172.236.32.29,172.236.32.34", + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "fr-par", + "label": "Paris, FR", "country": "fr", "capabilities": ["Linodes", "Block Storage + Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", + "Block Storage", "Object Storage", "GPU Linodes", "Kubernetes", "Kubernetes + Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", + "Premium Plans", "Placement Group", "StackScripts", "Linode Interfaces"], "monitors": + {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": + "ok", "resolvers": {"ipv4": "172.232.32.21,172.232.32.23,172.232.32.17,172.232.32.18,172.232.32.16,172.232.32.22,172.232.32.20,172.232.32.14,172.232.32.11,172.232.32.12", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "in-bom-2", - "label": "Mumbai 2, IN", "country": "in", "capabilities": ["Linodes", "Block + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "us-ord", + "label": "Chicago, IL", "country": "us", "capabilities": ["Linodes", "Block Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", - "Block Storage", "GPU Linodes", "Kubernetes", "Kubernetes Enterprise", "Cloud - Firewall", "Vlans", "VPCs", "Metadata", "Premium Plans", "Placement Group", - "StackScripts"], "monitors": {"alerts": [], "metrics": []}, "status": "ok", - "resolvers": {"ipv4": "172.236.171.41,172.236.171.42,172.236.171.25,172.236.171.44,172.236.171.26,172.236.171.45,172.236.171.24,172.236.171.43,172.236.171.27,172.236.171.28", + "Block Storage", "Object Storage", "GPU Linodes", "Kubernetes", "Kubernetes + Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", + "Premium Plans", "Placement Group", "StackScripts", "Linode Interfaces"], "monitors": + {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": + "ok", "resolvers": {"ipv4": "172.232.0.17,172.232.0.16,172.232.0.21,172.232.0.13,172.232.0.22,172.232.0.9,172.232.0.19,172.232.0.20,172.232.0.15,172.232.0.18", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "de-fra-2", - "label": "Frankfurt 2, DE", "country": "de", "capabilities": ["Linodes", "Block + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "us-iad", + "label": "Washington, DC", "country": "us", "capabilities": ["Linodes", "Block Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", - "Block Storage", "GPU Linodes", "Kubernetes", "Kubernetes Enterprise", "Cloud - Firewall", "Vlans", "VPCs", "Metadata", "Premium Plans", "Placement Group", - "StackScripts", "NETINT Quadra T1U"], "monitors": {"alerts": [], "metrics": - []}, "status": "ok", "resolvers": {"ipv4": "172.236.203.9,172.236.203.16,172.236.203.19,172.236.203.15,172.236.203.17,172.236.203.11,172.236.203.18,172.236.203.14,172.236.203.13,172.236.203.12", + "Block Storage", "Object Storage", "Kubernetes", "Kubernetes Enterprise", "Cloud + Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", "Premium Plans", + "Placement Group", "StackScripts", "Linode Interfaces"], "monitors": {"alerts": + ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": + {"ipv4": "139.144.192.62,139.144.192.60,139.144.192.61,139.144.192.53,139.144.192.54,139.144.192.67,139.144.192.69,139.144.192.66,139.144.192.52,139.144.192.68", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "sg-sin-2", - "label": "Singapore 2, SG", "country": "sg", "capabilities": ["Linodes", "Block - Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", - "Block Storage", "Object Storage", "GPU Linodes", "Kubernetes", "Kubernetes - Enterprise", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", "Metadata", - "Premium Plans", "Placement Group", "StackScripts"], "monitors": {"alerts": - ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": - {"ipv4": "172.236.129.8,172.236.129.42,172.236.129.41,172.236.129.19,172.236.129.46,172.236.129.23,172.236.129.48,172.236.129.20,172.236.129.21,172.236.129.47", + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "ap-southeast", + "label": "Sydney, AU", "country": "au", "capabilities": ["Linodes", "LA Disk + Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", + "Kubernetes", "Cloud Firewall", "Vlans", "Block Storage Migrations", "Managed + Databases", "Metadata", "Placement Group", "StackScripts", "Linode Interfaces"], + "monitors": {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, + "status": "ok", "resolvers": {"ipv4": "172.105.166.5,172.105.169.5,172.105.168.5,172.105.172.5,172.105.162.5,172.105.170.5,172.105.167.5,172.105.171.5,172.105.181.5,172.105.161.5", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "jp-tyo-3", - "label": "Tokyo 3, JP", "country": "jp", "capabilities": ["Linodes", "Block - Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", - "Block Storage", "Object Storage", "Kubernetes", "Kubernetes Enterprise", "Cloud - Firewall", "Vlans", "VPCs", "Metadata", "Premium Plans", "Placement Group", - "StackScripts"], "monitors": {"alerts": [], "metrics": []}, "status": "ok", - "resolvers": {"ipv4": "172.237.4.15,172.237.4.19,172.237.4.17,172.237.4.21,172.237.4.16,172.237.4.18,172.237.4.23,172.237.4.24,172.237.4.20,172.237.4.14", + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "ca-central", + "label": "Toronto, CA", "country": "ca", "capabilities": ["Linodes", "LA Disk + Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", + "Kubernetes", "Cloud Firewall", "Vlans", "Block Storage Migrations", "Managed + Databases", "Metadata", "Placement Group", "StackScripts", "Linode Interfaces"], + "monitors": {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, + "status": "ok", "resolvers": {"ipv4": "172.105.0.5,172.105.3.5,172.105.4.5,172.105.5.5,172.105.6.5,172.105.7.5,172.105.8.5,172.105.9.5,172.105.10.5,172.105.11.5", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "de-ber-1", - "label": "Berlin, DE", "country": "de", "capabilities": ["Linodes", "Disk Encryption", - "Cloud Firewall", "Vlans", "VPCs", "Metadata", "Distributed Plans"], "monitors": - {"alerts": [], "metrics": []}, "status": "ok", "resolvers": {"ipv4": "173.223.100.53,173.223.101.53", - "ipv6": "1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": - 0, "maximum_linodes_per_pg": 0, "maximum_linodes_per_flexible_pg": 0}, "site_type": - "distributed"}, {"id": "no-osl-1", "label": "Oslo, NO", "country": "no", "capabilities": - ["Linodes", "Block Storage Encryption", "Disk Encryption", "Backups", "NodeBalancers", - "Block Storage", "Kubernetes", "Cloud Firewall", "Vlans", "VPCs", "Managed Databases", - "Metadata", "Premium Plans", "Placement Group", "StackScripts"], "monitors": - {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": - "ok", "resolvers": {"ipv4": "172.238.140.33,172.238.140.25,172.238.140.30,172.238.140.31,172.238.140.26,172.238.140.28,172.238.140.34,172.238.140.32,172.238.140.27,172.238.140.29", + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "ap-west", + "label": "Mumbai, IN", "country": "in", "capabilities": ["Linodes", "LA Disk + Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", + "GPU Linodes", "Kubernetes", "Cloud Firewall", "Vlans", "Block Storage Migrations", + "Managed Databases", "Metadata", "Placement Group", "StackScripts", "Linode + Interfaces"], "monitors": {"alerts": ["Managed Databases"], "metrics": ["Managed + Databases"]}, "status": "ok", "resolvers": {"ipv4": "172.105.34.5,172.105.35.5,172.105.36.5,172.105.37.5,172.105.38.5,172.105.39.5,172.105.40.5,172.105.41.5,172.105.42.5,172.105.43.5", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "us-central", "label": "Dallas, TX", "country": "us", "capabilities": ["Linodes", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", "Kubernetes", "Cloud Firewall", "Vlans", "Block Storage Migrations", "Managed - Databases", "Metadata", "Placement Group", "StackScripts"], "monitors": {"alerts": - ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": - {"ipv4": "72.14.179.5,72.14.188.5,173.255.199.5,66.228.53.5,96.126.122.5,96.126.124.5,96.126.127.5,198.58.107.5,198.58.111.5,23.239.24.5", + Databases", "Metadata", "Placement Group", "StackScripts", "Maintenance Policy", + "Linode Interfaces"], "monitors": {"alerts": ["Managed Databases"], "metrics": + ["Managed Databases"]}, "status": "ok", "resolvers": {"ipv4": "72.14.179.5,72.14.188.5,173.255.199.5,66.228.53.5,96.126.122.5,96.126.124.5,96.126.127.5,198.58.107.5,198.58.111.5,23.239.24.5", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "us-west", - "label": "Fremont, CA", "country": "us", "capabilities": ["Linodes", "Block - Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", - "Block Storage", "Kubernetes", "Cloud Firewall", "Vlans", "Block Storage Migrations", - "Managed Databases", "Metadata", "Placement Group", "StackScripts"], "monitors": - {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": - "ok", "resolvers": {"ipv4": "173.230.145.5, 173.230.147.5, 173.230.155.5, 173.255.212.5, - 173.255.219.5, 173.255.241.5, 173.255.243.5, 173.255.244.5, 74.207.241.5, 74.207.242.5", - "ipv6": "1234::5678, 1234::5678, 1234::5678, 1234::5678, 1234::5678, - 1234::5678, 1234::5678, 1234::5678, 1234::5678, 1234::5678"}, "placement_group_limits": - {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": 5, "maximum_linodes_per_flexible_pg": - 5}, "site_type": "core"}, {"id": "us-southeast", "label": "Atlanta, GA", "country": - "us", "capabilities": ["Linodes", "LA Disk Encryption", "Disk Encryption", "Backups", - "NodeBalancers", "Block Storage", "Object Storage", "GPU Linodes", "Kubernetes", - "Cloud Firewall", "Vlans", "Block Storage Migrations", "Managed Databases", - "Metadata", "Placement Group", "StackScripts"], "monitors": {"alerts": ["Managed - Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": - {"ipv4": "74.207.231.5,173.230.128.5,173.230.129.5,173.230.136.5,173.230.140.5,66.228.59.5,66.228.62.5,50.116.35.5,50.116.41.5,23.239.18.5", + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "ap-northeast", + "label": "Tokyo 2, JP", "country": "jp", "capabilities": ["Linodes", "LA Disk + Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", + "Kubernetes", "Cloud Firewall", "Vlans", "Block Storage Migrations", "Managed + Databases", "Metadata", "Placement Group", "StackScripts", "Maintenance Policy", + "Linode Interfaces"], "monitors": {"alerts": ["Managed Databases"], "metrics": + ["Managed Databases"]}, "status": "ok", "resolvers": {"ipv4": "139.162.66.5,139.162.67.5,139.162.68.5,139.162.69.5,139.162.70.5,139.162.71.5,139.162.72.5,139.162.73.5,139.162.74.5,139.162.75.5", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "us-east", - "label": "Newark, NJ", "country": "us", "capabilities": ["Linodes", "LA Disk + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "eu-central", + "label": "Frankfurt, DE", "country": "de", "capabilities": ["Linodes", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", "Object Storage", "GPU Linodes", "Kubernetes", "Cloud Firewall", "Vlans", "Block - Storage Migrations", "Managed Databases", "Metadata", "Placement Group", "StackScripts"], - "monitors": {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, - "status": "ok", "resolvers": {"ipv4": "66.228.42.5,96.126.106.5,50.116.53.5,50.116.58.5,50.116.61.5,50.116.62.5,66.175.211.5,97.107.133.4,173.255.225.5,66.228.35.5", + Storage Migrations", "Managed Databases", "Metadata", "Placement Group", "StackScripts", + "Maintenance Policy", "Linode Interfaces"], "monitors": {"alerts": ["Managed + Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": + {"ipv4": "139.162.130.5,139.162.131.5,139.162.132.5,139.162.133.5,139.162.134.5,139.162.135.5,139.162.136.5,139.162.137.5,139.162.138.5,139.162.139.5", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "eu-west", - "label": "London, UK", "country": "gb", "capabilities": ["Linodes", "LA Disk - Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", - "Kubernetes", "Cloud Firewall", "Vlans", "Block Storage Migrations", "Metadata", - "Placement Group", "StackScripts"], "monitors": {"alerts": [], "metrics": []}, - "status": "ok", "resolvers": {"ipv4": "178.79.182.5, 176.58.107.5, 176.58.116.5, - 176.58.121.5, 151.236.220.5, 212.71.252.5, 212.71.253.5, 109.74.192.20, 109.74.193.20, - 109.74.194.20", "ipv6": "1234::5678, 1234::5678, 1234::5678, 1234::5678, - 1234::5678, 1234::5678, 1234::5678, 1234::5678, 1234::5678, 1234::5678"}, - "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "ap-south", "label": "Singapore, SG", "country": "sg", "capabilities": ["Linodes", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", "Object Storage", "GPU Linodes", "Kubernetes", "Cloud Firewall", "Vlans", "Block - Storage Migrations", "Metadata", "Placement Group", "StackScripts"], "monitors": - {"alerts": [], "metrics": []}, "status": "ok", "resolvers": {"ipv4": "139.162.11.5,139.162.13.5,139.162.14.5,139.162.15.5,139.162.16.5,139.162.21.5,139.162.27.5,103.3.60.18,103.3.60.19,103.3.60.20", + Storage Migrations", "Metadata", "Placement Group", "StackScripts", "Maintenance + Policy", "Linode Interfaces"], "monitors": {"alerts": [], "metrics": []}, "status": + "ok", "resolvers": {"ipv4": "139.162.11.5,139.162.13.5,139.162.14.5,139.162.15.5,139.162.16.5,139.162.21.5,139.162.27.5,103.3.60.18,103.3.60.19,103.3.60.20", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "eu-central", - "label": "Frankfurt, DE", "country": "de", "capabilities": ["Linodes", "LA Disk + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "eu-west", + "label": "London, UK", "country": "gb", "capabilities": ["Linodes", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", - "Object Storage", "GPU Linodes", "Kubernetes", "Cloud Firewall", "Vlans", "Block - Storage Migrations", "Managed Databases", "Metadata", "Placement Group", "StackScripts"], - "monitors": {"alerts": ["Managed Databases"], "metrics": ["Managed Databases"]}, - "status": "ok", "resolvers": {"ipv4": "139.162.130.5,139.162.131.5,139.162.132.5,139.162.133.5,139.162.134.5,139.162.135.5,139.162.136.5,139.162.137.5,139.162.138.5,139.162.139.5", + "Kubernetes", "Cloud Firewall", "Vlans", "Block Storage Migrations", "Metadata", + "Placement Group", "StackScripts", "Linode Interfaces"], "monitors": {"alerts": + [], "metrics": []}, "status": "ok", "resolvers": {"ipv4": "178.79.182.5, 176.58.107.5, + 176.58.116.5, 176.58.121.5, 151.236.220.5, 212.71.252.5, 212.71.253.5, 109.74.192.20, + 109.74.193.20, 109.74.194.20", "ipv6": "1234::5678, 1234::5678, 1234::5678, + 1234::5678, 1234::5678, 1234::5678, 1234::5678, 1234::5678, 1234::5678, + 1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, + "maximum_linodes_per_pg": 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": + "core"}, {"id": "us-east", "label": "Newark, NJ", "country": "us", "capabilities": + ["Linodes", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", + "Block Storage", "Object Storage", "GPU Linodes", "Kubernetes", "Cloud Firewall", + "Vlans", "Block Storage Migrations", "Managed Databases", "Metadata", "Placement + Group", "StackScripts", "Linode Interfaces"], "monitors": {"alerts": ["Managed + Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": + {"ipv4": "66.228.42.5,96.126.106.5,50.116.53.5,50.116.58.5,50.116.61.5,50.116.62.5,66.175.211.5,97.107.133.4,173.255.225.5,66.228.35.5", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "ap-northeast", - "label": "Tokyo 2, JP", "country": "jp", "capabilities": ["Linodes", "LA Disk + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "us-southeast", + "label": "Atlanta, GA", "country": "us", "capabilities": ["Linodes", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", "Block Storage", - "Kubernetes", "Cloud Firewall", "Vlans", "Block Storage Migrations", "Managed - Databases", "Metadata", "Placement Group", "StackScripts"], "monitors": {"alerts": - ["Managed Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": - {"ipv4": "139.162.66.5,139.162.67.5,139.162.68.5,139.162.69.5,139.162.70.5,139.162.71.5,139.162.72.5,139.162.73.5,139.162.74.5,139.162.75.5", + "Object Storage", "GPU Linodes", "Kubernetes", "Cloud Firewall", "Vlans", "Block + Storage Migrations", "Managed Databases", "Metadata", "Placement Group", "StackScripts", + "Maintenance Policy", "Linode Interfaces"], "monitors": {"alerts": ["Managed + Databases"], "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": + {"ipv4": "74.207.231.5,173.230.128.5,173.230.129.5,173.230.136.5,173.230.140.5,66.228.59.5,66.228.62.5,50.116.35.5,50.116.41.5,23.239.18.5", "ipv6": "1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678,1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": null, "maximum_linodes_per_pg": - 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}], "page": 1, - "pages": 1, "results": 43}' + 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": "core"}, {"id": "us-west", + "label": "Fremont, CA", "country": "us", "capabilities": ["Linodes", "Block + Storage Encryption", "LA Disk Encryption", "Disk Encryption", "Backups", "NodeBalancers", + "Block Storage", "Kubernetes", "Cloud Firewall", "Vlans", "Block Storage Migrations", + "Managed Databases", "Metadata", "Placement Group", "StackScripts", "Maintenance + Policy", "Linode Interfaces"], "monitors": {"alerts": ["Managed Databases"], + "metrics": ["Managed Databases"]}, "status": "ok", "resolvers": {"ipv4": "173.230.145.5, + 173.230.147.5, 173.230.155.5, 173.255.212.5, 173.255.219.5, 173.255.241.5, 173.255.243.5, + 173.255.244.5, 74.207.241.5, 74.207.242.5", "ipv6": "1234::5678, 1234::5678, + 1234::5678, 1234::5678, 1234::5678, 1234::5678, 1234::5678, 1234::5678, + 1234::5678, 1234::5678"}, "placement_group_limits": {"maximum_pgs_per_customer": + null, "maximum_linodes_per_pg": 5, "maximum_linodes_per_flexible_pg": 5}, "site_type": + "core"}], "page": 1, "pages": 1, "results": 32}' headers: Access-Control-Allow-Credentials: - "true" @@ -401,7 +348,7 @@ interactions: Content-Type: - application/json Expires: - - Tue, 12 Aug 2025 14:04:36 GMT + - Thu, 04 Sep 2025 13:34:37 GMT Pragma: - no-cache Strict-Transport-Security: @@ -418,10 +365,7 @@ interactions: - DENY - DENY X-Oauth-Scopes: - - account:read_write databases:read_write domains:read_write events:read_write - firewall:read_write images:read_write ips:read_write linodes:read_write lke:read_write - longview:read_write nodebalancers:read_write object_storage:read_write stackscripts:read_write - volumes:read_write vpc:read_write + - '*' X-Ratelimit-Limit: - "1840" X-Xss-Protection: @@ -430,7 +374,7 @@ interactions: code: 200 duration: "" - request: - body: '{"region":"ap-west","type":"g6-nanode-1","label":"go-test-ins-wo-disk-ev3x9078pfo9","firewall_id":3064284,"booted":false}' + body: '{"region":"nl-ams","type":"g6-nanode-1","label":"go-test-ins-wo-disk-9w5tzbz431w6","firewall_id":3171381,"booted":false}' form: {} headers: Accept: @@ -442,17 +386,18 @@ interactions: url: https://api.linode.com/v4beta/linode/instances method: POST response: - body: '{"id": 81825602, "label": "go-test-ins-wo-disk-ev3x9078pfo9", "group": + body: '{"id": 83044642, "label": "go-test-ins-wo-disk-9w5tzbz431w6", "group": "", "status": "provisioning", "created": "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", - "type": "g6-nanode-1", "ipv4": ["172.105.55.224"], "ipv6": "1234::5678/128", - "image": null, "region": "ap-west", "site_type": "core", "specs": {"disk": 25600, + "type": "g6-nanode-1", "ipv4": ["172.235.168.109"], "ipv6": "1234::5678/128", + "image": null, "region": "nl-ams", "site_type": "core", "specs": {"disk": 25600, "memory": 1024, "vcpus": 1, "gpus": 0, "transfer": 1000, "accelerated_devices": 0}, "alerts": {"cpu": 90, "network_in": 10, "network_out": 10, "transfer_quota": 80, "io": 10000}, "backups": {"enabled": true, "available": false, "schedule": {"day": null, "window": null}, "last_successful": null}, "hypervisor": "kvm", - "watchdog_enabled": true, "tags": [], "host_uuid": "455861e63d7ba3087c947b411156a2f21b8de1e8", + "watchdog_enabled": true, "tags": [], "host_uuid": "f9102f43fa74710343f11bc4536680c3b196fc58", "has_user_data": false, "placement_group": null, "disk_encryption": "enabled", - "lke_cluster_id": null, "capabilities": ["SMTP Enabled"]}' + "lke_cluster_id": null, "capabilities": ["Block Storage Encryption", "SMTP Enabled"], + "interface_generation": "legacy_config"}' headers: Access-Control-Allow-Credentials: - "true" @@ -475,7 +420,7 @@ interactions: Content-Type: - application/json Expires: - - Tue, 12 Aug 2025 14:04:36 GMT + - Thu, 04 Sep 2025 13:34:38 GMT Pragma: - no-cache Strict-Transport-Security: @@ -491,10 +436,7 @@ interactions: - DENY - DENY X-Oauth-Scopes: - - account:read_write databases:read_write domains:read_write events:read_write - firewall:read_write images:read_write ips:read_write linodes:read_write lke:read_write - longview:read_write nodebalancers:read_write object_storage:read_write stackscripts:read_write - volumes:read_write vpc:read_write + - '*' X-Ratelimit-Limit: - "20" X-Xss-Protection: @@ -503,7 +445,7 @@ interactions: code: 200 duration: "" - request: - body: '{"label":"go-test-conf-65e8yps1yb95","devices":{},"interfaces":null}' + body: '{"label":"go-test-conf-a1c0u41ws0o8","devices":{},"interfaces":null}' form: {} headers: Accept: @@ -512,11 +454,11 @@ interactions: - application/json User-Agent: - linodego/dev https://github.com/linode/linodego - url: https://api.linode.com/v4beta/linode/instances/81825602/configs + url: https://api.linode.com/v4beta/linode/instances/83044642/configs method: POST response: - body: '{"id": 85280425, "label": "go-test-conf-65e8yps1yb95", "helpers": {"updatedb_disabled": - true, "distro": true, "modules_dep": true, "network": true, "devtmpfs_automount": + body: '{"id": 86517906, "label": "go-test-conf-a1c0u41ws0o8", "helpers": {"updatedb_disabled": + true, "distro": true, "modules_dep": true, "network": false, "devtmpfs_automount": true}, "kernel": "linode/latest-64bit", "comments": "", "memory_limit": 0, "created": "2018-01-02T03:04:05", "updated": "2018-01-02T03:04:05", "root_device": "/dev/sda", "devices": {"sda": null, "sdb": null, "sdc": null, "sdd": null, "sde": null, @@ -540,13 +482,13 @@ interactions: Connection: - keep-alive Content-Length: - - "539" + - "540" Content-Security-Policy: - default-src 'none' Content-Type: - application/json Expires: - - Tue, 12 Aug 2025 14:04:37 GMT + - Thu, 04 Sep 2025 13:34:38 GMT Pragma: - no-cache Strict-Transport-Security: @@ -561,10 +503,7 @@ interactions: - DENY - DENY X-Oauth-Scopes: - - account:read_write databases:read_write domains:read_write events:read_write - firewall:read_write images:read_write ips:read_write linodes:read_write lke:read_write - longview:read_write nodebalancers:read_write object_storage:read_write stackscripts:read_write - volumes:read_write vpc:read_write + - '*' X-Ratelimit-Limit: - "1840" X-Xss-Protection: @@ -582,7 +521,7 @@ interactions: - application/json User-Agent: - linodego/dev https://github.com/linode/linodego - url: https://api.linode.com/v4beta/linode/instances/81825602/transfer/2025/8 + url: https://api.linode.com/v4beta/linode/instances/83044642/transfer/2025/9 method: GET response: body: '{"bytes_in": 0, "bytes_out": 0, "bytes_total": 0}' @@ -610,7 +549,7 @@ interactions: Content-Type: - application/json Expires: - - Tue, 12 Aug 2025 14:04:37 GMT + - Thu, 04 Sep 2025 13:34:39 GMT Pragma: - no-cache Strict-Transport-Security: @@ -626,10 +565,7 @@ interactions: - DENY - DENY X-Oauth-Scopes: - - account:read_write databases:read_write domains:read_write events:read_write - firewall:read_write images:read_write ips:read_write linodes:read_write lke:read_write - longview:read_write nodebalancers:read_write object_storage:read_write stackscripts:read_write - volumes:read_write vpc:read_write + - '*' X-Ratelimit-Limit: - "1840" X-Xss-Protection: @@ -647,7 +583,7 @@ interactions: - application/json User-Agent: - linodego/dev https://github.com/linode/linodego - url: https://api.linode.com/v4beta/linode/instances/81825602/transfer/2025/8 + url: https://api.linode.com/v4beta/linode/instances/83044642/transfer/2025/9 method: GET response: body: '{"bytes_in": 0, "bytes_out": 0, "bytes_total": 0}' @@ -675,7 +611,7 @@ interactions: Content-Type: - application/json Expires: - - Tue, 12 Aug 2025 14:04:37 GMT + - Thu, 04 Sep 2025 13:34:39 GMT Pragma: - no-cache Strict-Transport-Security: @@ -691,10 +627,7 @@ interactions: - DENY - DENY X-Oauth-Scopes: - - account:read_write databases:read_write domains:read_write events:read_write - firewall:read_write images:read_write ips:read_write linodes:read_write lke:read_write - longview:read_write nodebalancers:read_write object_storage:read_write stackscripts:read_write - volumes:read_write vpc:read_write + - '*' X-Ratelimit-Limit: - "1840" X-Xss-Protection: @@ -712,7 +645,7 @@ interactions: - application/json User-Agent: - linodego/dev https://github.com/linode/linodego - url: https://api.linode.com/v4beta/linode/instances/81825602 + url: https://api.linode.com/v4beta/linode/instances/83044642 method: DELETE response: body: '{}' @@ -740,7 +673,7 @@ interactions: Content-Type: - application/json Expires: - - Tue, 12 Aug 2025 14:04:45 GMT + - Thu, 04 Sep 2025 13:34:41 GMT Pragma: - no-cache Strict-Transport-Security: @@ -755,10 +688,7 @@ interactions: - DENY - DENY X-Oauth-Scopes: - - account:read_write databases:read_write domains:read_write events:read_write - firewall:read_write images:read_write ips:read_write linodes:read_write lke:read_write - longview:read_write nodebalancers:read_write object_storage:read_write stackscripts:read_write - volumes:read_write vpc:read_write + - '*' X-Ratelimit-Limit: - "1840" X-Xss-Protection: diff --git a/test/unit/fixtures/image_get_private_shared.json b/test/unit/fixtures/image_get_private_shared.json new file mode 100644 index 000000000..4fa063924 --- /dev/null +++ b/test/unit/fixtures/image_get_private_shared.json @@ -0,0 +1,38 @@ +{ + "capabilities": [ + "cloud-init", + "distributed-sites" + ], + "created": "2021-08-14T22:44:02", + "created_by": "linode", + "deprecated": false, + "description": "Example image description.", + "eol": null, + "expiry": null, + "id": "linode/debian11", + "is_public": true, + "label": "Debian 11", + "regions": [ + { + "region": "us-iad", + "status": "available" + } + ], + "size": 2500, + "status": "available", + "tags": [ + "repair-image", + "fix-1" + ], + "total_size": 1234567, + "type": "manual", + "updated": "2021-08-14T22:44:02", + "vendor": null, + "image_sharing":{ + "shared_with":{ + "sharegroup_count": 1, + "image_sharegroup_list_url": "/images/private/28747995/sharegroups" + }, + "shared_by": null + } +} \ No newline at end of file diff --git a/test/unit/fixtures/image_get_shared.json b/test/unit/fixtures/image_get_shared.json new file mode 100644 index 000000000..1fcc11088 --- /dev/null +++ b/test/unit/fixtures/image_get_shared.json @@ -0,0 +1,41 @@ +{ + "capabilities": [ + "cloud-init", + "distributed-sites" + ], + "created": "2021-08-14T22:44:02", + "created_by": null, + "deprecated": false, + "description": "Example image description.", + "eol": "2026-07-01T04:00:00", + "expiry": null, + "id": "linode/debian11", + "is_public": true, + "is_shared": null, + "label": "Debian 11", + "regions": [ + { + "region": "us-iad", + "status": "available" + } + ], + "size": 2500, + "status": "available", + "tags": [ + "repair-image", + "fix-1" + ], + "total_size": 1234567, + "type": "manual", + "updated": "2021-08-14T22:44:02", + "vendor": null, + "image_sharing":{ + "shared_with": null, + "shared_by": { + "sharegroup_id": 1, + "sharegroup_uuid": "0ee8e1c1-b19b-4052-9487-e3b13faac111", + "sharegroup_label": "test-group-minecraft-1", + "source_image_id": null + } + } +} \ No newline at end of file diff --git a/test/unit/fixtures/image_sharegroup_consumer_create_token.json b/test/unit/fixtures/image_sharegroup_consumer_create_token.json new file mode 100644 index 000000000..bcb1a1f2c --- /dev/null +++ b/test/unit/fixtures/image_sharegroup_consumer_create_token.json @@ -0,0 +1,12 @@ +{ + "token": "singleusetoken", + "token_uuid": "18db04bf-fd0f-4bf6-944a-1fc2ae044dc6", + "status": "active", + "label": "my_token", + "valid_for_sharegroup_uuid": "e3407945-5946-40f9-9732-d3c58b131ec0", + "created": "2025-07-01T04:00:00", + "updated": "2025-07-01T04:00:01", + "expiry": "2025-07-01T04:00:02", + "sharegroup_uuid": null, + "sharegroup_label": null +} diff --git a/test/unit/fixtures/image_sharegroup_consumer_get_sharegroup_by_token.json b/test/unit/fixtures/image_sharegroup_consumer_get_sharegroup_by_token.json new file mode 100644 index 000000000..7693fa882 --- /dev/null +++ b/test/unit/fixtures/image_sharegroup_consumer_get_sharegroup_by_token.json @@ -0,0 +1,9 @@ +{ + "id": 1, + "uuid": "967913ad-9379-4039-b166-31b6b1440019", + "label": "new_sharegroup_for_testing", + "description": "my description.", + "is_suspended": true, + "created": "2025-07-21T20:18:37", + "updated": "2025-07-22T18:09:07" +} diff --git a/test/unit/fixtures/image_sharegroup_consumer_get_sharegroup_images_by_token.json b/test/unit/fixtures/image_sharegroup_consumer_get_sharegroup_images_by_token.json new file mode 100644 index 000000000..d3c5f0820 --- /dev/null +++ b/test/unit/fixtures/image_sharegroup_consumer_get_sharegroup_images_by_token.json @@ -0,0 +1,45 @@ +{ + "data": + [ + { + "id": "shared/123", + "label": "producer defined share label", + "description": "some blob of legal text", + "created": "2024-12-03T01:51:24", + "updated": "2024-12-03T01:51:24", + "size": 1761, + "created_by": null, + "type": "manual", + "tags": [], + "is_public": false, + "is_shared": null, + "deprecated": false, + "vendor": null, + "expiry": null, + "eol": null, + "status": "available", + "capabilities": [ + "distributed-sites" + ], + "regions": [ + { + "region": "us-ord", + "status": "available" + } + ], + "total_size": 222, + "image_sharing":{ + "shared_with": null, + "shared_by": { + "sharegroup_id": 1234, + "sharegroup_uuid": "0ee8e1c1-b19b-4052-9487-e3b13faac111", + "sharegroup_label": "test-group-minecraft-1", + "source_image_id": null + } + } + } + ], + "page": 1, + "pages": 1, + "results": 1 +} diff --git a/test/unit/fixtures/image_sharegroup_consumer_get_token.json b/test/unit/fixtures/image_sharegroup_consumer_get_token.json new file mode 100644 index 000000000..c0dfad1cb --- /dev/null +++ b/test/unit/fixtures/image_sharegroup_consumer_get_token.json @@ -0,0 +1,11 @@ +{ + "token_uuid": "18db04bf-fd0f-4bf6-944a-1fc2ae044dc6", + "status": "active", + "label": "my_token", + "valid_for_sharegroup_uuid": "e3407945-5946-40f9-9732-d3c58b131ec0", + "created": "2025-07-01T04:00:00", + "updated": "2025-07-01T04:00:01", + "expiry": "2025-07-01T04:00:02", + "sharegroup_uuid": "e3407945-5946-40f9-9732-d3c58b131ec0", + "sharegroup_label": "my_sharegroup" +} diff --git a/test/unit/fixtures/image_sharegroup_consumer_list_tokens.json b/test/unit/fixtures/image_sharegroup_consumer_list_tokens.json new file mode 100644 index 000000000..adfd1c3fa --- /dev/null +++ b/test/unit/fixtures/image_sharegroup_consumer_list_tokens.json @@ -0,0 +1,19 @@ +{ + "data": + [ + { + "token_uuid": "18db04bf-fd0f-4bf6-944a-1fc2ae044dc6", + "status": "active", + "label": "my_token", + "valid_for_sharegroup_uuid": "e3407945-5946-40f9-9732-d3c58b131ec0", + "created": "2025-07-01T04:00:00", + "updated": "2025-07-01T04:00:01", + "expiry": "2025-07-01T04:00:02", + "sharegroup_uuid": "e3407945-5946-40f9-9732-d3c58b131ec0", + "sharegroup_label": "my_sharegroup" + } + ], + "page": 1, + "pages": 1, + "results": 1 +} diff --git a/test/unit/fixtures/image_sharegroup_consumer_update_token.json b/test/unit/fixtures/image_sharegroup_consumer_update_token.json new file mode 100644 index 000000000..949e8c144 --- /dev/null +++ b/test/unit/fixtures/image_sharegroup_consumer_update_token.json @@ -0,0 +1,11 @@ +{ + "token_uuid": "18db04bf-fd0f-4bf6-944a-1fc2ae044dc6", + "status": "active", + "label": "my_updated_token", + "valid_for_sharegroup_uuid": "e3407945-5946-40f9-9732-d3c58b131ec0", + "created": "2025-07-01T04:00:00", + "updated": "2025-07-01T04:00:01", + "expiry": "2025-07-01T04:00:02", + "sharegroup_uuid": null, + "sharegroup_label": null +} diff --git a/test/unit/fixtures/image_sharegroup_producer_add_images.json b/test/unit/fixtures/image_sharegroup_producer_add_images.json new file mode 100644 index 000000000..b56521be1 --- /dev/null +++ b/test/unit/fixtures/image_sharegroup_producer_add_images.json @@ -0,0 +1,43 @@ +[ + { + "capabilities": [ + "cloud-init", + "distributed-sites" + ], + "created": "2021-08-14T22:44:02", + "created_by": null, + "deprecated": false, + "description": "Example image description.", + "eol": "2026-07-01T04:00:00", + "expiry": null, + "id": "linode/debian11", + "is_public": true, + "is_shared": null, + "label": "Debian 11", + "regions": [ + { + "region": "us-iad", + "status": "available" + } + ], + "size": 2500, + "status": "available", + "tags": [ + "repair-image", + "fix-1" + ], + "total_size": 1234567, + "type": "manual", + "updated": "2021-08-14T22:44:02", + "vendor": null, + "image_sharing": { + "shared_with": null, + "shared_by": { + "sharegroup_id": 1234, + "sharegroup_uuid": "0ee8e1c1-b19b-4052-9487-e3b13faac111", + "sharegroup_label": "test-group-minecraft-1", + "source_image_id": null + } + } + } +] \ No newline at end of file diff --git a/test/unit/fixtures/image_sharegroup_producer_add_member.json b/test/unit/fixtures/image_sharegroup_producer_add_member.json new file mode 100644 index 000000000..4ecfafecb --- /dev/null +++ b/test/unit/fixtures/image_sharegroup_producer_add_member.json @@ -0,0 +1,8 @@ +{ + "token_uuid": "24wef-243qg-45wgg-q343q", + "status": "active", + "label": "CompanyEshop image sharing", + "created": "2016-03-16T17:30:49", + "updated": "2016-03-18T17:30:49", + "expiry": "2016-03-18T17:30:50" +} \ No newline at end of file diff --git a/test/unit/fixtures/image_sharegroup_producer_create.json b/test/unit/fixtures/image_sharegroup_producer_create.json new file mode 100644 index 000000000..996d3ebee --- /dev/null +++ b/test/unit/fixtures/image_sharegroup_producer_create.json @@ -0,0 +1,12 @@ +{ + "id": 1234, + "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "label": "a-cool-label", + "description": "This is the description.", + "is_suspended": false, + "images_count": 1, + "members_count": 0, + "created": "2025-07-01T04:00:00", + "updated": "2025-07-02T04:00:00", + "expiry": null +} \ No newline at end of file diff --git a/test/unit/fixtures/image_sharegroup_producer_get.json b/test/unit/fixtures/image_sharegroup_producer_get.json new file mode 100644 index 000000000..40aae1791 --- /dev/null +++ b/test/unit/fixtures/image_sharegroup_producer_get.json @@ -0,0 +1,12 @@ +{ + "id": 457, + "uuid": "eb5b9f0f-2e70-46a6-aee4-a081a2b99699", + "members_count": 1, + "images_count": 1, + "label": "some share other group label", + "description": "some larger text", + "is_suspended": false, + "created": "2026-01-16T17:30:49", + "updated": "2026-09-16T17:30:49", + "expiry": null +} diff --git a/test/unit/fixtures/image_sharegroup_producer_get_member.json b/test/unit/fixtures/image_sharegroup_producer_get_member.json new file mode 100644 index 000000000..470a116b5 --- /dev/null +++ b/test/unit/fixtures/image_sharegroup_producer_get_member.json @@ -0,0 +1,8 @@ +{ + "token_uuid": "24wef-243qg-45wgg-q343q", + "status": "active", + "label": "CompanyEshop image sharing", + "created": "2016-03-16T17:30:49", + "updated": "2016-03-18T17:30:49", + "expiry": "2016-03-18T17:30:50" +} diff --git a/test/unit/fixtures/image_sharegroup_producer_list_images.json b/test/unit/fixtures/image_sharegroup_producer_list_images.json new file mode 100644 index 000000000..d3c5f0820 --- /dev/null +++ b/test/unit/fixtures/image_sharegroup_producer_list_images.json @@ -0,0 +1,45 @@ +{ + "data": + [ + { + "id": "shared/123", + "label": "producer defined share label", + "description": "some blob of legal text", + "created": "2024-12-03T01:51:24", + "updated": "2024-12-03T01:51:24", + "size": 1761, + "created_by": null, + "type": "manual", + "tags": [], + "is_public": false, + "is_shared": null, + "deprecated": false, + "vendor": null, + "expiry": null, + "eol": null, + "status": "available", + "capabilities": [ + "distributed-sites" + ], + "regions": [ + { + "region": "us-ord", + "status": "available" + } + ], + "total_size": 222, + "image_sharing":{ + "shared_with": null, + "shared_by": { + "sharegroup_id": 1234, + "sharegroup_uuid": "0ee8e1c1-b19b-4052-9487-e3b13faac111", + "sharegroup_label": "test-group-minecraft-1", + "source_image_id": null + } + } + } + ], + "page": 1, + "pages": 1, + "results": 1 +} diff --git a/test/unit/fixtures/image_sharegroup_producer_list_members.json b/test/unit/fixtures/image_sharegroup_producer_list_members.json new file mode 100644 index 000000000..405fa78c5 --- /dev/null +++ b/test/unit/fixtures/image_sharegroup_producer_list_members.json @@ -0,0 +1,16 @@ +{ + "data": + [ + { + "token_uuid": "24wef-243qg-45wgg-q343q", + "status": "active", + "label": "CompanyEshop image sharing", + "created": "2016-03-16T17:30:49", + "updated": "2016-03-18T17:30:49", + "expiry": "2016-03-18T17:30:50" + } + ], + "page": 1, + "pages": 1, + "results": 1 +} diff --git a/test/unit/fixtures/image_sharegroup_producer_update.json b/test/unit/fixtures/image_sharegroup_producer_update.json new file mode 100644 index 000000000..a0a897fe9 --- /dev/null +++ b/test/unit/fixtures/image_sharegroup_producer_update.json @@ -0,0 +1,12 @@ +{ + "id": 1234, + "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "label": "a-cool-updated-label", + "description": "This is the updated description.", + "is_suspended": false, + "images_count": 1, + "members_count": 0, + "created": "2025-07-01T04:00:00", + "updated": "2025-07-02T04:00:00", + "expiry": null +} \ No newline at end of file diff --git a/test/unit/fixtures/image_sharegroup_producer_update_image.json b/test/unit/fixtures/image_sharegroup_producer_update_image.json new file mode 100644 index 000000000..1f5097435 --- /dev/null +++ b/test/unit/fixtures/image_sharegroup_producer_update_image.json @@ -0,0 +1,41 @@ +{ + "capabilities": [ + "cloud-init", + "distributed-sites" + ], + "created": "2021-08-14T22:44:02", + "created_by": null, + "deprecated": false, + "description": "Example updated image description.", + "eol": "2026-07-01T04:00:00", + "expiry": null, + "id": "linode/debian11", + "is_public": true, + "is_shared": null, + "label": "Debian 11", + "regions": [ + { + "region": "us-iad", + "status": "available" + } + ], + "size": 2500, + "status": "available", + "tags": [ + "repair-image", + "fix-1" + ], + "total_size": 1234567, + "type": "manual", + "updated": "2021-08-14T22:44:02", + "vendor": null, + "image_sharing": { + "shared_with": null, + "shared_by": { + "sharegroup_id": 1234, + "sharegroup_uuid": "0ee8e1c1-b19b-4052-9487-e3b13faac111", + "sharegroup_label": "test-group-minecraft-1", + "source_image_id": null + } + } +} \ No newline at end of file diff --git a/test/unit/fixtures/image_sharegroup_producer_update_member.json b/test/unit/fixtures/image_sharegroup_producer_update_member.json new file mode 100644 index 000000000..9e3e8a2e6 --- /dev/null +++ b/test/unit/fixtures/image_sharegroup_producer_update_member.json @@ -0,0 +1,8 @@ +{ + "token_uuid": "24wef-243qg-45wgg-q343q", + "status": "active", + "label": "CompanyEshop image sharing updated", + "created": "2016-03-16T17:30:49", + "updated": "2016-03-18T17:30:49", + "expiry": "2016-03-18T17:30:50" +} diff --git a/test/unit/fixtures/image_sharegroups_producer_list.json b/test/unit/fixtures/image_sharegroups_producer_list.json new file mode 100644 index 000000000..3d9cb67d6 --- /dev/null +++ b/test/unit/fixtures/image_sharegroups_producer_list.json @@ -0,0 +1,31 @@ +{ + "data": [ + { + "id": 456, + "uuid": "07358d70-0f95-4bd1-b2fd-fd7ab052baf4", + "label": "some share group label", + "description": "some desc", + "is_suspended": false, + "members_count": 12, + "images_count": 3, + "created": "2026-03-16T17:30:49", + "updated": "2026-04-16T17:30:49", + "expiry": null + }, + { + "id": 457, + "uuid": "eb5b9f0f-2e70-46a6-aee4-a081a2b99699", + "members_count": 1, + "images_count": 1, + "label": "some share other group label", + "description": "some_desc", + "is_suspended": false, + "created": "2026-01-16T17:30:49", + "updated": "2026-09-16T17:30:49", + "expiry": null + } + ], + "page": 1, + "pages": 1, + "results": 2 +} diff --git a/test/unit/fixtures/image_sharegroups_producer_list_containing_private_image.json b/test/unit/fixtures/image_sharegroups_producer_list_containing_private_image.json new file mode 100644 index 000000000..3d9cb67d6 --- /dev/null +++ b/test/unit/fixtures/image_sharegroups_producer_list_containing_private_image.json @@ -0,0 +1,31 @@ +{ + "data": [ + { + "id": 456, + "uuid": "07358d70-0f95-4bd1-b2fd-fd7ab052baf4", + "label": "some share group label", + "description": "some desc", + "is_suspended": false, + "members_count": 12, + "images_count": 3, + "created": "2026-03-16T17:30:49", + "updated": "2026-04-16T17:30:49", + "expiry": null + }, + { + "id": 457, + "uuid": "eb5b9f0f-2e70-46a6-aee4-a081a2b99699", + "members_count": 1, + "images_count": 1, + "label": "some share other group label", + "description": "some_desc", + "is_suspended": false, + "created": "2026-01-16T17:30:49", + "updated": "2026-09-16T17:30:49", + "expiry": null + } + ], + "page": 1, + "pages": 1, + "results": 2 +} diff --git a/test/unit/image_sharegroups_consumer_test.go b/test/unit/image_sharegroups_consumer_test.go new file mode 100644 index 000000000..5e1662309 --- /dev/null +++ b/test/unit/image_sharegroups_consumer_test.go @@ -0,0 +1,203 @@ +package unit + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/jarcoal/httpmock" + "github.com/linode/linodego" + "github.com/stretchr/testify/assert" +) + +func TestImageShareGroup_Consumer_CreateToken(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroup_consumer_create_token") + + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + requestData := linodego.ImageShareGroupCreateTokenOptions{ + ValidForShareGroupUUID: "e3407945-5946-40f9-9732-d3c58b131ec0", + Label: linodego.Pointer("my_token"), + } + + base.MockPost("images/sharegroups/tokens", fixtureData) + + token, err := base.Client.ImageShareGroupCreateToken(context.Background(), requestData) + assert.NoError(t, err) + + assert.Equal(t, "singleusetoken", token.Token) + assert.Equal(t, "18db04bf-fd0f-4bf6-944a-1fc2ae044dc6", token.TokenUUID) + assert.Equal(t, "active", token.Status) + assert.Equal(t, "my_token", token.Label) + assert.Equal(t, "e3407945-5946-40f9-9732-d3c58b131ec0", token.ValidForShareGroupUUID) + assert.Nil(t, token.ShareGroupUUID) + assert.Nil(t, token.ShareGroupLabel) + assert.Equal(t, "2025-07-01T04:00:00Z", token.Created.Format(time.RFC3339)) + assert.Equal(t, "2025-07-01T04:00:01Z", token.Updated.Format(time.RFC3339)) + assert.Equal(t, "2025-07-01T04:00:02Z", token.Expiry.Format(time.RFC3339)) +} + +func TestImageShareGroup_Consumer_UpdateToken(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroup_consumer_update_token") + + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + requestData := linodego.ImageShareGroupUpdateTokenOptions{ + Label: "my_updated_token", + } + + base.MockPut("images/sharegroups/tokens/18db04bf-fd0f-4bf6-944a-1fc2ae044dc6", fixtureData) + + token, err := base.Client.ImageShareGroupUpdateToken(context.Background(), "18db04bf-fd0f-4bf6-944a-1fc2ae044dc6", requestData) + assert.NoError(t, err) + + assert.Equal(t, "18db04bf-fd0f-4bf6-944a-1fc2ae044dc6", token.TokenUUID) + assert.Equal(t, "active", token.Status) + assert.Equal(t, "my_updated_token", token.Label) + assert.Equal(t, "e3407945-5946-40f9-9732-d3c58b131ec0", token.ValidForShareGroupUUID) + assert.Nil(t, token.ShareGroupUUID) + assert.Nil(t, token.ShareGroupLabel) + assert.Equal(t, "2025-07-01T04:00:00Z", token.Created.Format(time.RFC3339)) + assert.Equal(t, "2025-07-01T04:00:01Z", token.Updated.Format(time.RFC3339)) + assert.Equal(t, "2025-07-01T04:00:02Z", token.Expiry.Format(time.RFC3339)) +} + +func TestImageShareGroup_Consumer_List_Tokens(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroup_consumer_list_tokens") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("images/sharegroups/tokens", fixtureData) + + tokens, err := base.Client.ImageShareGroupListTokens(context.Background(), &linodego.ListOptions{}) + assert.NoError(t, err) + + token := tokens[0] + + assert.Len(t, tokens, 1) + + assert.Equal(t, "18db04bf-fd0f-4bf6-944a-1fc2ae044dc6", token.TokenUUID) + assert.Equal(t, "active", token.Status) + assert.Equal(t, "my_token", token.Label) + assert.Equal(t, "e3407945-5946-40f9-9732-d3c58b131ec0", token.ValidForShareGroupUUID) + assert.Equal(t, "e3407945-5946-40f9-9732-d3c58b131ec0", *token.ShareGroupUUID) + assert.Equal(t, "my_sharegroup", *token.ShareGroupLabel) + assert.Equal(t, "2025-07-01T04:00:00Z", token.Created.Format(time.RFC3339)) + assert.Equal(t, "2025-07-01T04:00:01Z", token.Updated.Format(time.RFC3339)) + assert.Equal(t, "2025-07-01T04:00:02Z", token.Expiry.Format(time.RFC3339)) +} + +func TestImageShareGroup_Consumer_Get_Token(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroup_consumer_get_token") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("images/sharegroups/tokens/18db04bf-fd0f-4bf6-944a-1fc2ae044dc6", fixtureData) + + token, err := base.Client.ImageShareGroupGetToken(context.Background(), "18db04bf-fd0f-4bf6-944a-1fc2ae044dc6") + assert.NoError(t, err) + + assert.Equal(t, "18db04bf-fd0f-4bf6-944a-1fc2ae044dc6", token.TokenUUID) + assert.Equal(t, "active", token.Status) + assert.Equal(t, "my_token", token.Label) + assert.Equal(t, "e3407945-5946-40f9-9732-d3c58b131ec0", token.ValidForShareGroupUUID) + assert.Equal(t, "e3407945-5946-40f9-9732-d3c58b131ec0", *token.ShareGroupUUID) + assert.Equal(t, "my_sharegroup", *token.ShareGroupLabel) + assert.Equal(t, "2025-07-01T04:00:00Z", token.Created.Format(time.RFC3339)) + assert.Equal(t, "2025-07-01T04:00:01Z", token.Updated.Format(time.RFC3339)) + assert.Equal(t, "2025-07-01T04:00:02Z", token.Expiry.Format(time.RFC3339)) +} + +func TestImageShareGroup_Consumer_Get_ShareGroup_ByToken(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroup_consumer_get_sharegroup_by_token") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("images/sharegroups/tokens/18db04bf-fd0f-4bf6-944a-1fc2ae044dc6/sharegroup", fixtureData) + + sharegroup, err := base.Client.ImageShareGroupGetByToken(context.Background(), "18db04bf-fd0f-4bf6-944a-1fc2ae044dc6") + assert.NoError(t, err) + + assert.Equal(t, 1, sharegroup.ID) + assert.Equal(t, "967913ad-9379-4039-b166-31b6b1440019", sharegroup.UUID) + assert.Equal(t, "new_sharegroup_for_testing", sharegroup.Label) + assert.Equal(t, "my description.", sharegroup.Description) + assert.Equal(t, true, sharegroup.IsSuspended) + assert.Equal(t, "2025-07-21T20:18:37Z", sharegroup.Created.Format(time.RFC3339)) + assert.Equal(t, "2025-07-22T18:09:07Z", sharegroup.Updated.Format(time.RFC3339)) +} + +func TestImageShareGroup_Consumer_Get_ShareGroup_Images_ByToken(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroup_consumer_get_sharegroup_images_by_token") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("images/sharegroups/tokens/18db04bf-fd0f-4bf6-944a-1fc2ae044dc6/sharegroup/images", fixtureData) + + images, err := base.Client.ImageShareGroupGetImagesByToken(context.Background(), "18db04bf-fd0f-4bf6-944a-1fc2ae044dc6", &linodego.ListOptions{}) + assert.NoError(t, err) + + image := images[0] + + assert.Len(t, images, 1) + + assert.Equal(t, "shared/123", image.ID) + assert.Equal(t, "producer defined share label", image.Label) + assert.Equal(t, "some blob of legal text", image.Description) + assert.Equal(t, "2024-12-03T01:51:24Z", image.Created.Format(time.RFC3339)) + assert.Equal(t, "2024-12-03T01:51:24Z", image.Updated.Format(time.RFC3339)) + assert.Equal(t, 1761, image.Size) + assert.Nil(t, image.CreatedBy) + assert.Equal(t, "manual", image.Type) + assert.Empty(t, image.Tags) + assert.False(t, image.IsPublic) + assert.Nil(t, image.IsShared) + assert.False(t, image.Deprecated) + assert.Nil(t, image.Vendor) + assert.Nil(t, image.Expiry) + assert.Nil(t, image.EOL) + assert.Equal(t, linodego.ImageStatus("available"), image.Status) + assert.Equal(t, []string{"distributed-sites"}, image.Capabilities) + assert.Equal(t, "us-ord", image.Regions[0].Region) + assert.Equal(t, linodego.ImageRegionStatus("available"), image.Regions[0].Status) + assert.Equal(t, 222, image.TotalSize) + assert.Nil(t, image.ImageSharing.SharedWith) + assert.Equal(t, 1234, image.ImageSharing.SharedBy.ShareGroupID) + assert.Equal(t, "0ee8e1c1-b19b-4052-9487-e3b13faac111", image.ImageSharing.SharedBy.ShareGroupUUID) + assert.Equal(t, "test-group-minecraft-1", image.ImageSharing.SharedBy.ShareGroupLabel) + assert.Nil(t, image.ImageSharing.SharedBy.SourceImageID) +} + +func TestImageShareGroup_Consumer_RemoveToken(t *testing.T) { + client := createMockClient(t) + + tokenUUID := "18db04bf-fd0f-4bf6-944a-1fc2ae044dc6" + + httpmock.RegisterRegexpResponder("DELETE", mockRequestURL(t, fmt.Sprintf("images/sharegroups/tokens/%s", tokenUUID)), + httpmock.NewStringResponder(200, "{}")) + + if err := client.ImageShareGroupRemoveToken(context.Background(), tokenUUID); err != nil { + t.Fatal(err) + } +} diff --git a/test/unit/image_sharegroups_producer_test.go b/test/unit/image_sharegroups_producer_test.go new file mode 100644 index 000000000..2cbb1228a --- /dev/null +++ b/test/unit/image_sharegroups_producer_test.go @@ -0,0 +1,468 @@ +package unit + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/jarcoal/httpmock" + "github.com/linode/linodego" + "github.com/stretchr/testify/assert" +) + +func TestImageShareGroup_Producer_Create(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroup_producer_create") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + requestData := linodego.ImageShareGroupCreateOptions{ + Label: "a-cool-label", + Description: linodego.Pointer("This is the description."), + Images: []linodego.ImageShareGroupImage{ + { + ImageID: "linode/debian11", + Label: linodego.Pointer("image-label"), + Description: linodego.Pointer("A description."), + }, + }, + } + + base.MockPost("images/sharegroups", fixtureData) + + imageShareGroup, err := base.Client.CreateImageShareGroup(context.Background(), requestData) + assert.NoError(t, err) + + assert.Equal(t, 1234, imageShareGroup.ID) + assert.Equal(t, "f47ac10b-58cc-4372-a567-0e02b2c3d479", imageShareGroup.UUID) + assert.Equal(t, "a-cool-label", imageShareGroup.Label) + assert.Equal(t, "This is the description.", imageShareGroup.Description) + assert.Equal(t, false, imageShareGroup.IsSuspended) + assert.Equal(t, 1, imageShareGroup.ImagesCount) + assert.Equal(t, 0, imageShareGroup.MembersCount) + assert.Equal(t, "2025-07-01T04:00:00Z", imageShareGroup.Created.Format(time.RFC3339)) + assert.Equal(t, "2025-07-02T04:00:00Z", imageShareGroup.Updated.Format(time.RFC3339)) + assert.Nil(t, imageShareGroup.Expiry) +} + +func TestImageShareGroup_Producer_Update(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroup_producer_update") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + requestData := linodego.ImageShareGroupUpdateOptions{ + Label: linodego.Pointer("a-cool-updated-label"), + Description: linodego.Pointer("This is the updated description."), + } + + base.MockPut("images/sharegroups/1234", fixtureData) + + imageShareGroup, err := base.Client.UpdateImageShareGroup(context.Background(), 1234, requestData) + assert.NoError(t, err) + + assert.Equal(t, 1234, imageShareGroup.ID) + assert.Equal(t, "f47ac10b-58cc-4372-a567-0e02b2c3d479", imageShareGroup.UUID) + assert.Equal(t, "a-cool-updated-label", imageShareGroup.Label) + assert.Equal(t, "This is the updated description.", imageShareGroup.Description) + assert.Equal(t, false, imageShareGroup.IsSuspended) + assert.Equal(t, 1, imageShareGroup.ImagesCount) + assert.Equal(t, 0, imageShareGroup.MembersCount) + assert.Equal(t, "2025-07-01T04:00:00Z", imageShareGroup.Created.Format(time.RFC3339)) + assert.Equal(t, "2025-07-02T04:00:00Z", imageShareGroup.Updated.Format(time.RFC3339)) + assert.Nil(t, imageShareGroup.Expiry) +} + +func TestImageShareGroup_Producer_AddImages(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroup_producer_add_images") + + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + requestData := linodego.ImageShareGroupAddImagesOptions{ + Images: []linodego.ImageShareGroupImage{ + { + ImageID: "linode/debian11", + Label: linodego.Pointer("image-label"), + Description: linodego.Pointer("A description."), + }, + }, + } + + base.MockPost("images/sharegroups/1234/images", fixtureData) + + images, err := base.Client.ImageShareGroupAddImages(context.Background(), 1234, requestData) + assert.NoError(t, err) + + image := images[0] + + assert.Equal(t, "linode/debian11", image.ID) + assert.Equal(t, "Debian 11", image.Label) + assert.Equal(t, "Example image description.", image.Description) + assert.Nil(t, image.Vendor) + assert.Equal(t, true, image.IsPublic) + assert.Equal(t, false, image.Deprecated) + assert.Equal(t, "available", string(image.Status)) + assert.Equal(t, "2021-08-14T22:44:02Z", image.Created.Format(time.RFC3339)) + assert.Equal(t, "2021-08-14T22:44:02Z", image.Updated.Format(time.RFC3339)) + assert.Equal(t, "2026-07-01T04:00:00Z", image.EOL.Format(time.RFC3339)) + assert.Equal(t, 2500, image.Size) + assert.Equal(t, 1234567, image.TotalSize) + + assert.Equal(t, 1234, image.ImageSharing.SharedBy.ShareGroupID) + assert.Equal(t, "0ee8e1c1-b19b-4052-9487-e3b13faac111", image.ImageSharing.SharedBy.ShareGroupUUID) + assert.Equal(t, "test-group-minecraft-1", image.ImageSharing.SharedBy.ShareGroupLabel) + assert.Nil(t, image.ImageSharing.SharedBy.SourceImageID) + assert.Nil(t, image.ImageSharing.SharedWith) + + assert.ElementsMatch(t, []string{"cloud-init", "distributed-sites"}, image.Capabilities) + + assert.Len(t, image.Regions, 1) + assert.Equal(t, "us-iad", image.Regions[0].Region) + assert.Equal(t, "available", string(image.Regions[0].Status)) + + assert.ElementsMatch(t, []string{"repair-image", "fix-1"}, image.Tags) +} + +func TestImageShareGroup_Producer_UpdateImage(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroup_producer_update_image") + + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + requestData := linodego.ImageShareGroupUpdateImageOptions{ + Description: linodego.Pointer("Example updated image description."), + } + + base.MockPut("images/sharegroups/1234/images/123", fixtureData) + + image, err := base.Client.ImageShareGroupUpdateImage(context.Background(), 1234, "123", requestData) + assert.NoError(t, err) + + assert.Equal(t, "linode/debian11", image.ID) + assert.Equal(t, "Debian 11", image.Label) + assert.Equal(t, "Example updated image description.", image.Description) + assert.Nil(t, image.Vendor) + assert.Equal(t, true, image.IsPublic) + assert.Equal(t, false, image.Deprecated) + assert.Equal(t, "available", string(image.Status)) + assert.Equal(t, "2021-08-14T22:44:02Z", image.Created.Format(time.RFC3339)) + assert.Equal(t, "2021-08-14T22:44:02Z", image.Updated.Format(time.RFC3339)) + assert.Equal(t, "2026-07-01T04:00:00Z", image.EOL.Format(time.RFC3339)) + assert.Equal(t, 2500, image.Size) + assert.Equal(t, 1234567, image.TotalSize) + + assert.Equal(t, 1234, image.ImageSharing.SharedBy.ShareGroupID) + assert.Equal(t, "0ee8e1c1-b19b-4052-9487-e3b13faac111", image.ImageSharing.SharedBy.ShareGroupUUID) + assert.Equal(t, "test-group-minecraft-1", image.ImageSharing.SharedBy.ShareGroupLabel) + assert.Nil(t, image.ImageSharing.SharedBy.SourceImageID) + assert.Nil(t, image.ImageSharing.SharedWith) + + assert.ElementsMatch(t, []string{"cloud-init", "distributed-sites"}, image.Capabilities) + + assert.Len(t, image.Regions, 1) + assert.Equal(t, "us-iad", image.Regions[0].Region) + assert.Equal(t, "available", string(image.Regions[0].Status)) + + assert.ElementsMatch(t, []string{"repair-image", "fix-1"}, image.Tags) +} + +func TestImageShareGroup_Producer_AddMember(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroup_producer_add_member") + + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + requestData := linodego.ImageShareGroupAddMemberOptions{ + Token: "my-token", + Label: "CompanyEshop image sharing", + } + + base.MockPost("images/sharegroups/1234/members", fixtureData) + + member, err := base.Client.ImageShareGroupAddMember(context.Background(), 1234, requestData) + assert.NoError(t, err) + + assert.Equal(t, "24wef-243qg-45wgg-q343q", member.TokenUUID) + assert.Equal(t, "active", member.Status) + assert.Equal(t, "CompanyEshop image sharing", member.Label) + assert.Equal(t, "2016-03-16T17:30:49Z", member.Created.Format(time.RFC3339)) + assert.Equal(t, "2016-03-18T17:30:49Z", member.Updated.Format(time.RFC3339)) + assert.Equal(t, "2016-03-18T17:30:50Z", member.Expiry.Format(time.RFC3339)) +} + +func TestImageShareGroup_Producer_List_ContainingPrivateImage(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroups_producer_list_containing_private_image") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("images/1234/sharegroups", fixtureData) + + imageShareGroups, err := base.Client.ListImageShareGroupsContainingPrivateImage(context.Background(), 1234, &linodego.ListOptions{}) + assert.NoError(t, err) + + assert.Len(t, imageShareGroups, 2) + + // First share group + assert.Equal(t, 456, imageShareGroups[0].ID) + assert.Equal(t, "07358d70-0f95-4bd1-b2fd-fd7ab052baf4", imageShareGroups[0].UUID) + assert.Equal(t, "some share group label", imageShareGroups[0].Label) + assert.Equal(t, "some desc", imageShareGroups[0].Description) + assert.False(t, imageShareGroups[0].IsSuspended) + assert.Equal(t, 12, imageShareGroups[0].MembersCount) + assert.Equal(t, 3, imageShareGroups[0].ImagesCount) + assert.Equal(t, "2026-03-16T17:30:49Z", imageShareGroups[0].Created.Format(time.RFC3339)) + assert.Equal(t, "2026-04-16T17:30:49Z", imageShareGroups[0].Updated.Format(time.RFC3339)) + assert.Nil(t, imageShareGroups[0].Expiry) + + // Second share group + assert.Equal(t, 457, imageShareGroups[1].ID) + assert.Equal(t, "eb5b9f0f-2e70-46a6-aee4-a081a2b99699", imageShareGroups[1].UUID) + assert.Equal(t, "some share other group label", imageShareGroups[1].Label) + assert.Equal(t, "some_desc", imageShareGroups[1].Description) + assert.False(t, imageShareGroups[1].IsSuspended) + assert.Equal(t, 1, imageShareGroups[1].MembersCount) + assert.Equal(t, 1, imageShareGroups[1].ImagesCount) + assert.Equal(t, "2026-01-16T17:30:49Z", imageShareGroups[1].Created.Format(time.RFC3339)) + assert.Equal(t, "2026-09-16T17:30:49Z", imageShareGroups[1].Updated.Format(time.RFC3339)) + assert.Nil(t, imageShareGroups[1].Expiry) +} + +func TestImageShareGroup_Producer_List(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroups_producer_list") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("images/sharegroups", fixtureData) + + imageShareGroups, err := base.Client.ListImageShareGroups(context.Background(), &linodego.ListOptions{}) + assert.NoError(t, err) + + assert.Len(t, imageShareGroups, 2) + + // First share group + assert.Equal(t, 456, imageShareGroups[0].ID) + assert.Equal(t, "07358d70-0f95-4bd1-b2fd-fd7ab052baf4", imageShareGroups[0].UUID) + assert.Equal(t, "some share group label", imageShareGroups[0].Label) + assert.Equal(t, "some desc", imageShareGroups[0].Description) + assert.False(t, imageShareGroups[0].IsSuspended) + assert.Equal(t, 12, imageShareGroups[0].MembersCount) + assert.Equal(t, 3, imageShareGroups[0].ImagesCount) + assert.Equal(t, "2026-03-16T17:30:49Z", imageShareGroups[0].Created.Format(time.RFC3339)) + assert.Equal(t, "2026-04-16T17:30:49Z", imageShareGroups[0].Updated.Format(time.RFC3339)) + assert.Nil(t, imageShareGroups[0].Expiry) + + // Second share group + assert.Equal(t, 457, imageShareGroups[1].ID) + assert.Equal(t, "eb5b9f0f-2e70-46a6-aee4-a081a2b99699", imageShareGroups[1].UUID) + assert.Equal(t, "some share other group label", imageShareGroups[1].Label) + assert.Equal(t, "some_desc", imageShareGroups[1].Description) + assert.False(t, imageShareGroups[1].IsSuspended) + assert.Equal(t, 1, imageShareGroups[1].MembersCount) + assert.Equal(t, 1, imageShareGroups[1].ImagesCount) + assert.Equal(t, "2026-01-16T17:30:49Z", imageShareGroups[1].Created.Format(time.RFC3339)) + assert.Equal(t, "2026-09-16T17:30:49Z", imageShareGroups[1].Updated.Format(time.RFC3339)) + assert.Nil(t, imageShareGroups[1].Expiry) +} + +func TestImageShareGroup_Producer_Get(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroup_producer_get") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet(formatMockAPIPath("images/sharegroups/%d", 457), fixtureData) + + imageShareGroup, err := base.Client.GetImageShareGroup(context.Background(), 457) + assert.NoError(t, err) + + assert.Equal(t, 457, imageShareGroup.ID) + assert.Equal(t, "eb5b9f0f-2e70-46a6-aee4-a081a2b99699", imageShareGroup.UUID) + assert.Equal(t, "some share other group label", imageShareGroup.Label) + assert.Equal(t, "some larger text", imageShareGroup.Description) + assert.False(t, imageShareGroup.IsSuspended) + assert.Equal(t, 1, imageShareGroup.MembersCount) + assert.Equal(t, 1, imageShareGroup.ImagesCount) + assert.Equal(t, "2026-01-16T17:30:49Z", imageShareGroup.Created.Format(time.RFC3339)) + assert.Equal(t, "2026-09-16T17:30:49Z", imageShareGroup.Updated.Format(time.RFC3339)) + assert.Nil(t, imageShareGroup.Expiry) +} + +func TestImageShareGroup_Producer_List_Images(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroup_producer_list_images") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("images/sharegroups/1234/images", fixtureData) + + images, err := base.Client.ImageShareGroupListImages(context.Background(), 1234, &linodego.ListOptions{}) + assert.NoError(t, err) + + image := images[0] + + assert.Len(t, images, 1) + + assert.Equal(t, "shared/123", image.ID) + assert.Equal(t, "producer defined share label", image.Label) + assert.Equal(t, "some blob of legal text", image.Description) + assert.Equal(t, "2024-12-03T01:51:24Z", image.Created.Format(time.RFC3339)) + assert.Equal(t, "2024-12-03T01:51:24Z", image.Updated.Format(time.RFC3339)) + assert.Equal(t, 1761, image.Size) + assert.Nil(t, image.CreatedBy) + assert.Equal(t, "manual", image.Type) + assert.Empty(t, image.Tags) + assert.False(t, image.IsPublic) + assert.Nil(t, image.IsShared) + assert.False(t, image.Deprecated) + assert.Nil(t, image.Vendor) + assert.Nil(t, image.Expiry) + assert.Nil(t, image.EOL) + assert.Equal(t, linodego.ImageStatus("available"), image.Status) + assert.Equal(t, []string{"distributed-sites"}, image.Capabilities) + assert.Equal(t, "us-ord", image.Regions[0].Region) + assert.Equal(t, linodego.ImageRegionStatus("available"), image.Regions[0].Status) + assert.Equal(t, 222, image.TotalSize) + assert.Nil(t, image.ImageSharing.SharedWith) + assert.Equal(t, 1234, image.ImageSharing.SharedBy.ShareGroupID) + assert.Equal(t, "0ee8e1c1-b19b-4052-9487-e3b13faac111", image.ImageSharing.SharedBy.ShareGroupUUID) + assert.Equal(t, "test-group-minecraft-1", image.ImageSharing.SharedBy.ShareGroupLabel) + assert.Nil(t, image.ImageSharing.SharedBy.SourceImageID) +} + +func TestImageShareGroup_Producer_List_Members(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroup_producer_list_members") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("images/sharegroups/1234/members", fixtureData) + + members, err := base.Client.ImageShareGroupListMembers(context.Background(), 1234, &linodego.ListOptions{}) + assert.NoError(t, err) + + member := members[0] + + assert.Len(t, members, 1) + + assert.Equal(t, "24wef-243qg-45wgg-q343q", member.TokenUUID) + assert.Equal(t, "active", member.Status) + assert.Equal(t, "CompanyEshop image sharing", member.Label) + assert.Equal(t, "2016-03-16T17:30:49Z", member.Created.Format(time.RFC3339)) + assert.Equal(t, "2016-03-18T17:30:49Z", member.Updated.Format(time.RFC3339)) + assert.Equal(t, "2016-03-18T17:30:50Z", member.Expiry.Format(time.RFC3339)) +} + +func TestImageShareGroup_Producer_Get_Member(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroup_producer_get_member") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + base.MockGet("images/sharegroups/1234/members/24wef-243qg-45wgg-q343q", fixtureData) + + member, err := base.Client.ImageShareGroupGetMember(context.Background(), 1234, "24wef-243qg-45wgg-q343q") + assert.NoError(t, err) + + assert.Equal(t, "24wef-243qg-45wgg-q343q", member.TokenUUID) + assert.Equal(t, "active", member.Status) + assert.Equal(t, "CompanyEshop image sharing", member.Label) + assert.Equal(t, "2016-03-16T17:30:49Z", member.Created.Format(time.RFC3339)) + assert.Equal(t, "2016-03-18T17:30:49Z", member.Updated.Format(time.RFC3339)) + assert.Equal(t, "2016-03-18T17:30:50Z", member.Expiry.Format(time.RFC3339)) +} + +func TestImageShareGroup_Producer_UpdateMember(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_sharegroup_producer_update_member") + + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + requestData := linodego.ImageShareGroupUpdateMemberOptions{ + Label: "CompanyEshop image sharing updated", + } + + base.MockPut("images/sharegroups/1234/members/24wef-243qg-45wgg-q343q", fixtureData) + + member, err := base.Client.ImageShareGroupUpdateMember(context.Background(), 1234, "24wef-243qg-45wgg-q343q", requestData) + assert.NoError(t, err) + + assert.Equal(t, "24wef-243qg-45wgg-q343q", member.TokenUUID) + assert.Equal(t, "active", member.Status) + assert.Equal(t, "CompanyEshop image sharing updated", member.Label) + assert.Equal(t, "2016-03-16T17:30:49Z", member.Created.Format(time.RFC3339)) + assert.Equal(t, "2016-03-18T17:30:49Z", member.Updated.Format(time.RFC3339)) + assert.Equal(t, "2016-03-18T17:30:50Z", member.Expiry.Format(time.RFC3339)) +} + +func TestImageShareGroup_Producer_Delete(t *testing.T) { + client := createMockClient(t) + + imageShareGroupID := 123 + + httpmock.RegisterRegexpResponder("DELETE", mockRequestURL(t, fmt.Sprintf("images/sharegroups/%d", imageShareGroupID)), + httpmock.NewStringResponder(200, "{}")) + + if err := client.DeleteImageShareGroup(context.Background(), imageShareGroupID); err != nil { + t.Fatal(err) + } +} + +func TestImageShareGroup_Producer_RemoveImage(t *testing.T) { + client := createMockClient(t) + + imageShareGroupID := 123 + imageID := "123" + + httpmock.RegisterRegexpResponder("DELETE", mockRequestURL(t, fmt.Sprintf("images/sharegroups/%d/images/%s", imageShareGroupID, imageID)), + httpmock.NewStringResponder(200, "{}")) + + if err := client.ImageShareGroupRemoveImage(context.Background(), imageShareGroupID, imageID); err != nil { + t.Fatal(err) + } +} + +func TestImageShareGroup_Producer_RemoveMember(t *testing.T) { + client := createMockClient(t) + + imageShareGroupID := 123 + tokenUUID := "18db04bf-fd0f-4bf6-944a-1fc2ae044dc6" + + httpmock.RegisterRegexpResponder("DELETE", mockRequestURL(t, fmt.Sprintf("images/sharegroups/%d/members/%s", imageShareGroupID, tokenUUID)), + httpmock.NewStringResponder(200, "{}")) + + if err := client.ImageShareGroupRemoveMember(context.Background(), imageShareGroupID, tokenUUID); err != nil { + t.Fatal(err) + } +} diff --git a/test/unit/images_test.go b/test/unit/images_test.go index a79baa948..e710f90cf 100644 --- a/test/unit/images_test.go +++ b/test/unit/images_test.go @@ -35,7 +35,7 @@ func TestImage_List(t *testing.T) { assert.Equal(t, true, image.IsPublic) assert.Equal(t, "2026-07-01T04:00:00Z", image.EOL.Format(time.RFC3339)) assert.ElementsMatch(t, []string{"repair-image", "fix-1"}, image.Tags) - assert.Equal(t, "Debian", image.Vendor) + assert.Equal(t, "Debian", *image.Vendor) assert.False(t, image.Deprecated) expectedCapabilities := []string{"cloud-init", "distributed-sites"} assert.ElementsMatch(t, expectedCapabilities, image.Capabilities) @@ -62,7 +62,7 @@ func TestImage_Get(t *testing.T) { assert.Equal(t, "linode/debian11", image.ID) assert.Equal(t, "Debian 11", image.Label) assert.Equal(t, "Example image description.", image.Description) - assert.Equal(t, "Debian", image.Vendor) + assert.Equal(t, "Debian", *image.Vendor) assert.Equal(t, true, image.IsPublic) assert.Equal(t, false, image.Deprecated) assert.Equal(t, "available", string(image.Status)) @@ -81,6 +81,90 @@ func TestImage_Get(t *testing.T) { assert.ElementsMatch(t, []string{"repair-image", "fix-1"}, image.Tags) } +func TestImage_GetPrivateShared(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_get_private_shared") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + imageID := "123" + + base.MockGet(formatMockAPIPath("images/%s", imageID), fixtureData) + + image, err := base.Client.GetImage(context.Background(), imageID) + assert.NoError(t, err) + + assert.Equal(t, "linode/debian11", image.ID) + assert.Equal(t, "Debian 11", image.Label) + assert.Equal(t, "Example image description.", image.Description) + assert.Nil(t, image.Vendor) + assert.Equal(t, true, image.IsPublic) + assert.Equal(t, false, image.Deprecated) + assert.Equal(t, "available", string(image.Status)) + assert.Equal(t, "2021-08-14T22:44:02Z", image.Created.Format(time.RFC3339)) + assert.Equal(t, "2021-08-14T22:44:02Z", image.Updated.Format(time.RFC3339)) + assert.Nil(t, image.EOL) + assert.Equal(t, 2500, image.Size) + assert.Equal(t, 1234567, image.TotalSize) + + assert.Equal(t, 1, image.ImageSharing.SharedWith.ShareGroupCount) + assert.Equal(t, "/images/private/28747995/sharegroups", image.ImageSharing.SharedWith.ImageShareGroupListURL) + assert.Nil(t, image.ImageSharing.SharedBy) + + assert.ElementsMatch(t, []string{"cloud-init", "distributed-sites"}, image.Capabilities) + + assert.Len(t, image.Regions, 1) + assert.Equal(t, "us-iad", image.Regions[0].Region) + assert.Equal(t, "available", string(image.Regions[0].Status)) + + assert.ElementsMatch(t, []string{"repair-image", "fix-1"}, image.Tags) +} + +func TestImage_GetShared(t *testing.T) { + fixtureData, err := fixtures.GetFixture("image_get_shared") + assert.NoError(t, err) + + var base ClientBaseCase + base.SetUp(t) + defer base.TearDown(t) + + imageID := "123" + + base.MockGet(formatMockAPIPath("images/%s", imageID), fixtureData) + + image, err := base.Client.GetImage(context.Background(), imageID) + assert.NoError(t, err) + + assert.Equal(t, "linode/debian11", image.ID) + assert.Equal(t, "Debian 11", image.Label) + assert.Equal(t, "Example image description.", image.Description) + assert.Nil(t, image.Vendor) + assert.Equal(t, true, image.IsPublic) + assert.Equal(t, false, image.Deprecated) + assert.Equal(t, "available", string(image.Status)) + assert.Equal(t, "2021-08-14T22:44:02Z", image.Created.Format(time.RFC3339)) + assert.Equal(t, "2021-08-14T22:44:02Z", image.Updated.Format(time.RFC3339)) + assert.Equal(t, "2026-07-01T04:00:00Z", image.EOL.Format(time.RFC3339)) + assert.Equal(t, 2500, image.Size) + assert.Equal(t, 1234567, image.TotalSize) + + assert.Equal(t, 1, image.ImageSharing.SharedBy.ShareGroupID) + assert.Equal(t, "0ee8e1c1-b19b-4052-9487-e3b13faac111", image.ImageSharing.SharedBy.ShareGroupUUID) + assert.Equal(t, "test-group-minecraft-1", image.ImageSharing.SharedBy.ShareGroupLabel) + assert.Nil(t, image.ImageSharing.SharedBy.SourceImageID) + assert.Nil(t, image.ImageSharing.SharedWith) + + assert.ElementsMatch(t, []string{"cloud-init", "distributed-sites"}, image.Capabilities) + + assert.Len(t, image.Regions, 1) + assert.Equal(t, "us-iad", image.Regions[0].Region) + assert.Equal(t, "available", string(image.Regions[0].Status)) + + assert.ElementsMatch(t, []string{"repair-image", "fix-1"}, image.Tags) +} + func TestImage_Create(t *testing.T) { fixtureData, err := fixtures.GetFixture("image_create") assert.NoError(t, err) @@ -105,7 +189,7 @@ func TestImage_Create(t *testing.T) { assert.Equal(t, "linode/debian11", image.ID) assert.Equal(t, "Debian 11", image.Label) assert.Equal(t, "Example image description.", image.Description) - assert.Equal(t, "Debian", image.Vendor) + assert.Equal(t, "Debian", *image.Vendor) assert.Equal(t, true, image.IsPublic) assert.Equal(t, false, image.Deprecated) assert.Equal(t, "available", string(image.Status)) @@ -149,7 +233,7 @@ func TestImage_Update(t *testing.T) { assert.Equal(t, "linode/debian11", image.ID) assert.Equal(t, "Debian 11", image.Label) assert.Equal(t, "Example image description.", image.Description) - assert.Equal(t, "Debian", image.Vendor) + assert.Equal(t, "Debian", *image.Vendor) assert.Equal(t, true, image.IsPublic) assert.Equal(t, false, image.Deprecated) assert.Equal(t, "available", string(image.Status)) @@ -199,7 +283,7 @@ func TestImage_Upload(t *testing.T) { assert.Equal(t, true, image.IsPublic) assert.Equal(t, "2026-07-01T04:00:00Z", image.EOL.Format(time.RFC3339)) assert.ElementsMatch(t, []string{"repair-image", "fix-1"}, image.Tags) - assert.Equal(t, "Debian", image.Vendor) + assert.Equal(t, "Debian", *image.Vendor) assert.False(t, image.Deprecated) expectedCapabilities := []string{"cloud-init", "distributed-sites"} assert.ElementsMatch(t, expectedCapabilities, image.Capabilities) @@ -247,7 +331,7 @@ func TestImage_Replicate(t *testing.T) { assert.Equal(t, "linode/debian11", image.ID) assert.Equal(t, "Debian 11", image.Label) assert.Equal(t, "Example image description.", image.Description) - assert.Equal(t, "Debian", image.Vendor) + assert.Equal(t, "Debian", *image.Vendor) assert.Equal(t, true, image.IsPublic) assert.Equal(t, false, image.Deprecated) assert.Equal(t, "available", string(image.Status))