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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/api-v4": Upcoming Features
---

Reserved IPs: Added new API endpoints ([#13517](https://github.com/linode/manager/pull/13517))
98 changes: 95 additions & 3 deletions packages/api-v4/src/networking/networking.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
allocateIPSchema,
assignAddressesSchema,
reserveIPSchema,
shareAddressesSchema,
updateIPSchema,
} from '@linode/validation/lib/networking.schema';
Expand All @@ -16,12 +17,14 @@ import Request, {

import type { Filter, ResourcePage as Page, Params } from '../types';
import type {
AllocateIPPayload,
CreateIPv6RangePayload,
IPAddress,
IPAssignmentPayload,
IPRange,
IPRangeInformation,
IPSharingPayload,
ReserveIPPayload,
} from './types';

/**
Expand Down Expand Up @@ -52,16 +55,24 @@ export const getIP = (address: string) =>
* Sets RDNS on an IP Address. Forward DNS must already be set up for reverse
* DNS to be applied. If you set the RDNS to null for public IPv4 addresses,
* it will be reset to the default members.linode.com RDNS value.
* Also setting β€œreserved” field to true converts an Ephemeral IP to an Reserved IP.
* setting β€œreserved” field set to false converts a Reserved IP to an Ephemeral IP.
* An Ephemeral IP is an IP that’s assigned to a Linode but not reserved.
*
* @param address { string } The address to operate on.
* @param rdns { string } The reverse DNS assigned to this address. For public
* IPv4 addresses, this will be set to a default value provided by Linode if not
* explicitly set.
* @param reserved { boolean } Whether to reserve the IP address.
*/
export const updateIP = (address: string, rdns: null | string = null) =>
export const updateIP = (
address: string,
rdns: null | string = null,
reserved?: boolean,
) =>
Request<IPAddress>(
setURL(`${API_ROOT}/networking/ips/${encodeURIComponent(address)}`),
setData({ rdns }, updateIPSchema),
setData({ rdns, reserved }, updateIPSchema),
setMethod('PUT'),
);

Expand All @@ -77,8 +88,11 @@ export const updateIP = (address: string, rdns: null | string = null) =>
* address.
* @param payload.linode_id { number } The ID of a Linode you you have access to
* that this address will be allocated to.
* @param payload.reserved { boolean } Whether to reserve the IP address.
* @param payload.region { string } The ID of the Region in which this address * will be allocated.
* Required when reserving an IP address, not required when allocating an ephemeral IP address.
*/
export const allocateIp = (payload: any) =>
export const allocateIp = (payload: AllocateIPPayload) =>
Request<IPAddress>(
setURL(`${API_ROOT}/networking/ips/`),
setData(payload, allocateIPSchema),
Expand Down Expand Up @@ -194,3 +208,81 @@ export const createIPv6Range = (payload: CreateIPv6RangePayload) => {
setData(payload),
);
};

// Reserve IP queries
/**
* getReservedIps
*
* Returns a paginated list of all Reserved IP addresses on this account.
*/
export const getReservedIPs = (params?: Params, filters?: Filter) =>
Request<Page<IPAddress>>(
setMethod('GET'),
setParams(params),
setXFilter(filters),
setURL(`${BETA_API_ROOT}/networking/reserved/ips`),
);

/**
* Returns information about a single Reserved IP Address on your Account.
*
* @param address { string } The address to operate on.
*/
export const getReservedIP = (address: string) =>
Request<IPAddress>(
setURL(
`${BETA_API_ROOT}/networking/reserved/ips/${encodeURIComponent(address)}`,
),
setMethod('GET'),
);

/**
* Tags associated with the reserved IP can be updated.
* The tags associated with the reserved IP will be completely replaced with the values specified in the request body,
* rather than just adding additional tags to what’s already there
*
* @param address { string } The address to operate on.
* @param tags { string[] | null } The tags to associate with this reserved IP. If null, all tags will be removed.
*/
export const updateReservedIP = (
address: string,
tags: null | string[] = null,
) =>
Request<IPAddress>(
setURL(
`${BETA_API_ROOT}/networking/reserved/ips/${encodeURIComponent(address)}`,
),
setData({ tags }),
setMethod('PUT'),
);

/**
* Makes one of the IP address available in the provided region as reserved.
* Only IPv4 addresses may be reserved through this endpoint.
*
* @param payload { Object }
* @param payload.region { string } The ID of the Region in which these
* assignments are to take place. All IPs and Linodes must exist in this Region.
* @param payload.tags { string[] } A list of tags to associate with this reserved IP.
*/
export const reserveIP = (payload: ReserveIPPayload) =>
Request<IPAddress>(
setURL(`${BETA_API_ROOT}/networking/reserved/ips`),
setData(payload, reserveIPSchema),
setMethod('POST'),
);

/**
* unReserveIP
*
* Unreserves an IP address, making it available for general use.
* Any Tag associations will be removed when the IP address is un-reserved via this endpoint.
*
*/
export const unReserveIP = (ipAddress: string) =>
Request<object>(
setURL(
`${BETA_API_ROOT}/networking/reserved/ips/${encodeURIComponent(ipAddress)}`,
),
setMethod('DELETE'),
);
15 changes: 15 additions & 0 deletions packages/api-v4/src/networking/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ export interface IPAddress {
public: boolean;
rdns: null | string;
region: string;
reserved: boolean;
subnet_mask: string;
tags: string[];
type: string;
vpc_nat_1_1?: null | {
address: string;
Expand All @@ -16,6 +18,14 @@ export interface IPAddress {
};
}

export interface AllocateIPPayload {
linode_id: number;
public: boolean;
region?: string;
reserved?: boolean;
type: string;
}

export interface IPRangeBaseData {
prefix: number;
range: string;
Expand Down Expand Up @@ -51,3 +61,8 @@ export interface CreateIPv6RangePayload {
prefix_length: IPv6Prefix;
route_target?: string;
}

export interface ReserveIPPayload {
region: string;
tags?: string[];
}
4 changes: 4 additions & 0 deletions packages/api-v4/src/tags/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ export interface Tag {
}

export interface TagRequest {
domains?: number[];
label: string;
linodes?: number[];
nodebalancers?: number[];
reserved_ipv4_addresses?: string[];
volumes?: number[];
}
2 changes: 2 additions & 0 deletions packages/manager/src/factories/networking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ export const ipAddressFactory = Factory.Sync.makeFactory<IPAddress>({
region: 'us-east',
subnet_mask: Factory.each((id) => `192.168.1.${id + 3}`),
type: 'ipv4',
reserved: false,
tags: [],
});
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ const ipAddressForVPC = (
region: ip.region,
subnet_mask: ip.subnet_mask,
type: ipType,
reserved: false,
tags: [],
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/queries": Upcoming Features
---

Reserved IPs: Added queries for Reserved IPs ([#13517](https://github.com/linode/manager/pull/13517))
90 changes: 89 additions & 1 deletion packages/queries/src/networking/networking.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { createIPv6Range, getIPv6RangeInfo } from '@linode/api-v4';
import {
createIPv6Range,
getIPv6RangeInfo,
getReservedIP,
getReservedIPs,
reserveIP,
unReserveIP,
updateReservedIP,
} from '@linode/api-v4';
import { createQueryKeys } from '@lukemorales/query-key-factory';
import {
keepPreviousData,
useMutation,
useQueries,
useQuery,
Expand All @@ -19,6 +28,8 @@ import type {
IPRange,
IPRangeInformation,
Params,
ReserveIPPayload,
ResourcePage,
} from '@linode/api-v4';

export const networkingQueries = createQueryKeys('networking', {
Expand All @@ -39,6 +50,14 @@ export const networkingQueries = createQueryKeys('networking', {
},
queryKey: null,
},
reservedIPs: (params: Params = {}, filter: Filter = {}) => ({
queryFn: () => getReservedIPs(params, filter),
queryKey: [params, filter],
}),
reservedIP: (address: string) => ({
queryFn: () => getReservedIP(address),
queryKey: [address],
}),
});

export const useAllIPsQuery = (
Expand Down Expand Up @@ -119,3 +138,72 @@ export const useCreateIPv6RangeMutation = () => {
},
});
};

export const useReservedIPsQuery = (
params?: Params,
filter?: Filter,
enabled: boolean = true,
) => {
return useQuery<ResourcePage<IPAddress>, APIError[]>({
...networkingQueries.reservedIPs(params, filter),
enabled,
placeholderData: keepPreviousData,
});
};

export const useReservedIPQuery = (address: string, enabled: boolean = true) =>
useQuery<IPAddress, APIError[]>({
...networkingQueries.reservedIP(address),
enabled,
});

export const useReserveIPMutation = () => {
const queryClient = useQueryClient();
return useMutation<IPAddress, APIError[], ReserveIPPayload>({
mutationFn: reserveIP,
onSuccess(reservedIP) {
queryClient.invalidateQueries({
queryKey: networkingQueries.reservedIPs._def,
});
queryClient.setQueryData<IPAddress>(
networkingQueries.reservedIP(reservedIP.address).queryKey,
reservedIP,
);
},
});
};

export const useUpdateReservedIPMutation = (address: string) => {
const queryClient = useQueryClient();
return useMutation<
IPAddress,
APIError[],
{ address: string; tags: null | string[] }
>({
mutationFn: (data) => updateReservedIP(address, data.tags),
onSuccess(reservedIP) {
queryClient.invalidateQueries({
queryKey: networkingQueries.reservedIPs._def,
});
queryClient.setQueryData<IPAddress>(
networkingQueries.reservedIP(reservedIP.address).queryKey,
reservedIP,
);
},
});
};

export const useUnReserveIPMutation = (address: string) => {
const queryClient = useQueryClient();
return useMutation<object, APIError[]>({
mutationFn: () => unReserveIP(address),
onSuccess() {
queryClient.invalidateQueries({
queryKey: networkingQueries.reservedIPs._def,
});
queryClient.removeQueries({
queryKey: networkingQueries.reservedIP(address).queryKey,
});
},
});
};
6 changes: 6 additions & 0 deletions packages/utilities/src/factories/linodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ export const linodeIPFactory = Factory.Sync.makeFactory<LinodeIPsResponse>({
region: 'us-southeast',
subnet_mask: '255.255.255.0',
type: 'ipv4',
reserved: false,
tags: [],
},
],
reserved: [],
Expand Down Expand Up @@ -123,6 +125,8 @@ export const linodeIPFactory = Factory.Sync.makeFactory<LinodeIPsResponse>({
region: 'us-southeast',
subnet_mask: 'ffff:ffff:ffff:ffff::',
type: 'ipv6',
reserved: false,
tags: [],
},
slaac: {
address: '2001:DB8::0000',
Expand All @@ -135,6 +139,8 @@ export const linodeIPFactory = Factory.Sync.makeFactory<LinodeIPsResponse>({
region: 'us-southeast',
subnet_mask: 'ffff:ffff:ffff:ffff::',
type: 'ipv6',
reserved: false,
tags: [],
},
vpc: [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/validation": Upcoming Features
---

Reserved IPs: Updated schemas to handle reserved IPs API changes ([#13517](https://github.com/linode/manager/pull/13517))
1 change: 1 addition & 0 deletions packages/validation/src/linodes.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ export const IPAllocationSchema = object({
.required('IP address type (IPv4) is required.')
.oneOf(['ipv4'], 'Only IPv4 addresses can be allocated.'),
public: boolean().required('Must specify public or private IP address.'),
address: string().optional(),
});

export const CreateSnapshotSchema = object({
Expand Down
Loading
Loading