diff --git a/packages/api-v4/.changeset/pr-12984-upcoming-features-1761308732683.md b/packages/api-v4/.changeset/pr-12984-upcoming-features-1761308732683.md new file mode 100644 index 00000000000..588b2a0b406 --- /dev/null +++ b/packages/api-v4/.changeset/pr-12984-upcoming-features-1761308732683.md @@ -0,0 +1,5 @@ +--- +"@linode/api-v4": Upcoming Features +--- + +Add endpoints for `/v4/images/sharegroups/members` and `/v4/images/sharegroups/tokens` ([#12984](https://github.com/linode/manager/pull/12984)) diff --git a/packages/api-v4/src/images/sharegroup.ts b/packages/api-v4/src/images/sharegroup.ts new file mode 100644 index 00000000000..811ef234bd5 --- /dev/null +++ b/packages/api-v4/src/images/sharegroup.ts @@ -0,0 +1,237 @@ +import { + addSharegroupMemberSchema, + generateSharegroupTokenSchema, + updateSharegroupMemberSchema, + updateSharegroupTokenSchema, +} from '@linode/validation/lib/images.schema'; + +import { BETA_API_ROOT } from '../constants'; +import Request, { + setData, + setMethod, + setParams, + setURL, + setXFilter, +} from '../request'; + +import type { Filter, ResourcePage as Page, Params } from '../types'; +import type { + AddSharegroupMemberPayload, + GenerateSharegroupTokenPayload, + Image, + Sharegroup, + SharegroupMember, + SharegroupToken, + UpdateSharegroupMemberPayload, +} from './types'; + +/** + * Add Member to the Sharegroup + * + * @param sharegroupId {string} ID of the Sharegroup to add member + * @param data {AddSharegroupMemberPayload} the Member details + */ +export const addMembersToSharegroup = ( + sharegroupId: number, + data: AddSharegroupMemberPayload, +) => { + return Request( + setURL( + `${BETA_API_ROOT}/images/sharegroups/${encodeURIComponent(sharegroupId)}/images`, + ), + setMethod('POST'), + setData(data, addSharegroupMemberSchema), + ); +}; + +/** + * Generate user token for the Sharegroup + * + * @param data {GenerateSharegroupTokenPayload} the token details + */ +export const generateSharegroupToken = ( + data: GenerateSharegroupTokenPayload, +) => { + return Request( + setURL(`${BETA_API_ROOT}/images/sharegroups/tokens`), + setMethod('POST'), + setData(data, generateSharegroupTokenSchema), + ); +}; + +/** + * Get details of the Sharegroup the token has been accepted into + * + * @param token_uuid {string} Token UUID of the user + */ +export const getSharegroupFromToken = (token_uuid: string) => { + Request( + setURL( + `${BETA_API_ROOT}/images/sharegroups/tokens/${encodeURIComponent(token_uuid)}/sharegroup`, + ), + setMethod('GET'), + ); +}; + +/** + * Get a paginated list of Sharegroup Images the token has been accepted into + * + * @param token_uuid {string} Token UUID of the user + */ +export const getSharegroupImagesFromToken = ( + token_uuid: string, + params: Params = {}, + filters: Filter = {}, +) => { + Request>( + setURL( + `${BETA_API_ROOT}/images/sharegroups/tokens/${encodeURIComponent(token_uuid)}/sharegroups/images`, + ), + setMethod('GET'), + setParams(params), + setXFilter(filters), + ); +}; + +/** + * Get a paginated list of members part of the Sharegroup + * + * @param sharegroupId {string} ID of the Sharegroup to look up + */ +export const getSharegroupMembers = ( + sharegroupId: string, + params: Params = {}, + filters: Filter = {}, +) => { + Request>( + setURL( + `${BETA_API_ROOT}/images/sharegroups/${encodeURIComponent(sharegroupId)}/members`, + ), + setMethod('GET'), + setParams(params), + setXFilter(filters), + ); +}; + +/** + * Get member details of a user from the Sharegroup + * + * @param sharegroupId {string} ID of the Sharegroup to look up + * @param token_uuid {string} Token UUID of the user to look up + */ +export const getSharegroupMember = ( + sharegroupId: string, + token_uuid: string, +) => { + Request( + setURL( + `${BETA_API_ROOT}/images/sharegroups/${encodeURIComponent(sharegroupId)}/members/${encodeURIComponent(token_uuid)}`, + ), + setMethod('GET'), + ); +}; + +/** + * Returns a paginated list of tokens created by the user + */ +export const getUserSharegroupTokens = ( + params: Params = {}, + filters: Filter = {}, +) => { + Request>( + setURL(`${BETA_API_ROOT}/images/sharegroups/tokens`), + setMethod('GET'), + setParams(params), + setXFilter(filters), + ); +}; + +/** + * Get details about a specific token created by the user + * + * @param token_uuid Token UUID of the user to look up + */ +export const getUserSharegroupToken = (token_uuid: string) => { + Request( + setURL( + `${BETA_API_ROOT}/images/sharegroups/tokens/${encodeURIComponent(token_uuid)}`, + ), + setMethod('GET'), + ); +}; + +/** + * Update a user token's label + * + * @param token_uuid {string} token UUID of the user + * @param data {UpdateSharegroupMemberPayload} the updated label + */ +export const updateSharegroupToken = ( + token_uuid: string, + data: UpdateSharegroupMemberPayload, +) => { + return Request( + setURL( + `${BETA_API_ROOT}/images/sharegroups/tokens/${encodeURIComponent(token_uuid)}`, + ), + setMethod('PUT'), + setData(data, updateSharegroupTokenSchema), + ); +}; + +/** + * Update a Sharegroup member's label + * + * @param token_uuid {string} token UUID of the user + * @param data {UpdateSharegroupMemberPayload} the updated label + */ +interface UpdateSharegroupMember { + data: UpdateSharegroupMemberPayload; + sharegroupId: string; + token_uuid: string; +} + +export const updateSharegroupMember = ({ + sharegroupId, + token_uuid, + data, +}: UpdateSharegroupMember) => { + return Request( + setURL( + `${BETA_API_ROOT}/images/sharegroups/${encodeURIComponent(sharegroupId)}/members/${encodeURIComponent(token_uuid)}`, + ), + setMethod('PUT'), + setData(data, updateSharegroupMemberSchema), + ); +}; + +/** + * Delete a user token + * + * @param token_uuid {string} Token UUID of the user to delete + */ +export const deleteSharegroupToken = (token_uuid: string) => { + return Request<{}>( + setURL( + `${BETA_API_ROOT}/images/sharegroups/tokens/${encodeURIComponent(token_uuid)}`, + ), + setMethod('DELETE'), + ); +}; + +/** + * Delete a sharegroup Member + * + * @param token_uuid {string} Token UUID of the member to delete + */ +export const deleteSharegroupMember = ( + sharegroupId: string, + token_uuid: string, +) => { + return Request<{}>( + setURL( + `${BETA_API_ROOT}/images/sharegroups/${encodeURIComponent(sharegroupId)}/members/${encodeURIComponent(token_uuid)}`, + ), + setMethod('DELETE'), + ); +}; diff --git a/packages/api-v4/src/images/types.ts b/packages/api-v4/src/images/types.ts index 8eb4c68fbe9..f77301675e7 100644 --- a/packages/api-v4/src/images/types.ts +++ b/packages/api-v4/src/images/types.ts @@ -4,6 +4,8 @@ export type ImageCapabilities = 'cloud-init' | 'distributed-sites'; type ImageType = 'automatic' | 'manual'; +type SharegroupMemberStatus = 'active' | 'revoked'; + export type ImageRegionStatus = | 'available' | 'creating' @@ -18,6 +20,19 @@ export interface ImageRegion { status: ImageRegionStatus; } +export interface ImageSharingData { + shared_by: null | { + sharegroup_id: number; + sharegroup_label: string; + sharegroup_uuid: string; + source_image_id: number; + }; + shared_with: null | { + sharegroup_count: number; + sharegroup_list_url: string; + }; +} + export interface Image { /** * A list of the capabilities of this image. @@ -59,11 +74,21 @@ export interface Image { */ id: string; + /** + * Image sharing attributes for private and shared images. + */ + image_sharing?: ImageSharingData; + /** * Whether this image is marked for public distribution. */ is_public: boolean; + /** + * Whether this image has a shared copy. + */ + is_shared?: boolean; + /** * A short description of this image. */ @@ -157,3 +182,143 @@ export interface UpdateImageRegionsPayload { */ regions: string[]; } + +export interface Sharegroup { + /** + * The timestamp of when the Sharegroup was created + */ + created: string; + /** + * A detailed description for the Sharegroup + */ + description: string; + /** + * The timestamp of when the Sharegroup would expire + */ + expiry?: string; + /** + * The ID of the this Sharegroup. + */ + id: number; + /** + * The number of images shared in the Sharegroup + */ + images_count?: number; + /** + * A boolean that indicates if the Sharegroup is suspended + */ + is_suspended: boolean; + /** + * A short title for the Sharegroup + */ + label: string; + /** + * The number of members present in the Sharegroup + */ + members_count?: number; + /** + * The timestamp of when the Sharegroup was last updated + */ + updated: string; + /** + * A unique identifier for the sharegroup which can be used to generate member tokens + */ + uuid: string; +} + +export interface AddSharegroupMemberPayload { + /** + * The title given to the user in the sharegroup + */ + label: string; + /** + * The user token shared by the user to join the sharegroup + */ + token: string; +} + +export type UpdateSharegroupMemberPayload = Omit< + AddSharegroupMemberPayload, + 'token' +>; + +export interface SharegroupMember { + /** + * The timestamp of when the member was added to the sharegroup + */ + created: string; + /** + * The timestamp of when the member's token expires + */ + expiry: string; + /** + * The title given to the user in the sharegroup + */ + label: string; + /** + * The status of the member in the current sharegroup + */ + status: SharegroupMemberStatus; + /** + * A unique identifier for member tokens + */ + token_uuid: string; + /** + * The timestamp of when the member's information was last updated + */ + updated: string; +} + +export interface GenerateSharegroupTokenPayload { + /** + * The title given to the user in the sharegroup + */ + label?: string; + /** + * The sharegroup UUID for which a user token will be generated + */ + valid_for_sharegroup_uuid: string; +} + +export interface SharegroupToken { + /** + * The timestamp of when the token was created + */ + created: string; + /** + * The timestamp of when the token will expire + */ + expiry: string; + /** + * The title given to the user in the sharegroup + */ + label: string; + /** + * The sharegroup label this token is created for + */ + sharegroup_label: string; + /** + * The sharegroup UUID the token is created for + */ + sharegroup_uuid: string; + /** + * The current status of this token + */ + status: string; + /** + * A unique member token to join the sharegroup + */ + token: string; + /** + * A unique identifier for each token generated + */ + token_uuid: string; + /** + * The timestamp of when the token was last updated + */ + updated: string; + /** + * The sharegroup UUID the token is valid for + */ + valid_for_sharegroup_uuid: string; +} diff --git a/packages/validation/.changeset/pr-12984-upcoming-features-1761308818735.md b/packages/validation/.changeset/pr-12984-upcoming-features-1761308818735.md new file mode 100644 index 00000000000..98a1993c429 --- /dev/null +++ b/packages/validation/.changeset/pr-12984-upcoming-features-1761308818735.md @@ -0,0 +1,5 @@ +--- +"@linode/validation": Upcoming Features +--- + +Add validation schemas for creating and updating Sharegroup Members and Tokens ([#12984](https://github.com/linode/manager/pull/12984)) diff --git a/packages/validation/src/images.schema.ts b/packages/validation/src/images.schema.ts index 9ac35a1c027..e5e84bae480 100644 --- a/packages/validation/src/images.schema.ts +++ b/packages/validation/src/images.schema.ts @@ -39,3 +39,23 @@ export const updateImageRegionsSchema = object({ .required('Regions are required.') .min(1, 'Must specify at least one region.'), }); + +export const addSharegroupMemberSchema = object({ + token: string().required('Token is required.'), + label: labelSchema.required('Label is required.'), +}); + +export const generateSharegroupTokenSchema = object({ + label: labelSchema.optional(), + valid_for_sharegroup_uuid: boolean().required( + 'Valid sharegroup UUID required.', + ), +}); + +export const updateSharegroupTokenSchema = object({ + label: labelSchema.required('Label is required.'), +}); + +export const updateSharegroupMemberSchema = object({ + label: labelSchema.required('Label is required.'), +});