1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-05 05:45:22 +02:00

feat(app): limit the docker API version supported by the frontend (#11855)
Some checks are pending
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
ci / build_images (map[arch:arm platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Waiting to run
ci / build_images (map[arch:s390x platform:linux version:]) (push) Waiting to run
ci / build_manifests (push) Blocked by required conditions
/ triage (push) Waiting to run
Lint / Run linters (push) Waiting to run
Test / test-client (push) Waiting to run
Test / test-server (map[arch:amd64 platform:linux]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Waiting to run
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Waiting to run
Test / test-server (map[arch:arm64 platform:linux]) (push) Waiting to run

This commit is contained in:
LP B 2024-06-10 20:54:31 +02:00 committed by GitHub
parent 4ba16f1b04
commit 6a8e6734f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
212 changed files with 4439 additions and 3281 deletions

View file

@ -4,15 +4,18 @@ import { useQueryClient } from '@tanstack/react-query';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { AccessControlPanel } from '@/react/portainer/access-control/AccessControlPanel/AccessControlPanel';
import { ResourceControlType } from '@/react/portainer/access-control/types';
import { DockerContainer } from '@/react/docker/containers/types';
import { ContainerListViewModel } from '@/react/docker/containers/types';
import { ResourceControlViewModel } from '@/react/portainer/access-control/models/ResourceControlViewModel';
import { useContainers } from '@/react/docker/containers/queries/containers';
import { useContainers } from '@/react/docker/containers/queries/useContainers';
import { notifySuccess } from '@/portainer/services/notifications';
import { PageHeader } from '@@/PageHeader';
import { useNetwork, useDeleteNetwork } from '../queries';
import { useDeleteNetwork } from '../queries/useDeleteNetworkMutation';
import { isSystemNetwork } from '../network.helper';
import { NetworkResponseContainers } from '../types';
import { queryKeys } from '../queries/queryKeys';
import { useNetwork } from '../queries/useNetwork';
import { NetworkDetailsTable } from './NetworkDetailsTable';
import { NetworkOptionsTable } from './NetworkOptionsTable';
@ -27,7 +30,7 @@ export function ItemView() {
} = useCurrentStateAndParams();
const environmentId = useEnvironmentId();
const networkQuery = useNetwork(environmentId, networkId, { nodeName });
const deleteNetworkMutation = useDeleteNetwork();
const deleteNetworkMutation = useDeleteNetwork(environmentId);
const containersQuery = useContainers(environmentId, {
filters: {
network: [networkId],
@ -69,13 +72,9 @@ export function ItemView() {
<AccessControlPanel
onUpdateSuccess={() =>
queryClient.invalidateQueries([
'environments',
environmentId,
'docker',
'networks',
networkId,
])
queryClient.invalidateQueries(
queryKeys.item(environmentId, networkId)
)
}
resourceControl={resourceControl}
resourceType={ResourceControlType.Network}
@ -95,9 +94,10 @@ export function ItemView() {
async function onRemoveNetworkClicked() {
deleteNetworkMutation.mutate(
{ environmentId, networkId },
{ networkId },
{
onSuccess: () => {
notifySuccess('Network successfully removed', networkId);
router.stateService.go('docker.networks');
},
}
@ -107,7 +107,7 @@ export function ItemView() {
function filterContainersInNetwork(
networkContainers?: NetworkResponseContainers,
containers: DockerContainer[] = []
containers: ContainerListViewModel[] = []
) {
if (!networkContainers) {
return [];

View file

@ -3,6 +3,7 @@ import { Server, Trash2 } from 'lucide-react';
import { Authorized } from '@/react/hooks/useUser';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { Icon } from '@/react/components/Icon';
import { notifySuccess } from '@/portainer/services/notifications';
import { TableContainer, TableTitle } from '@@/datatables';
import { DetailsTable } from '@@/DetailsTable';
@ -10,7 +11,7 @@ import { Button } from '@@/buttons';
import { Link } from '@@/Link';
import { NetworkContainer, NetworkId } from '../types';
import { useDisconnectContainer } from '../queries';
import { useDisconnectContainer } from '../queries/useDisconnectContainerMutation';
type Props = {
networkContainers: NetworkContainer[];
@ -33,7 +34,10 @@ export function NetworkContainersTable({
environmentId,
networkId,
}: Props) {
const disconnectContainer = useDisconnectContainer();
const disconnectContainer = useDisconnectContainer({
environmentId,
networkId,
});
if (networkContainers.length === 0) {
return null;
@ -72,11 +76,18 @@ export function NetworkContainersTable({
color="dangerlight"
onClick={() => {
if (container.Id) {
disconnectContainer.mutate({
containerId: container.Id,
environmentId,
networkId,
});
disconnectContainer.mutate(
{
containerId: container.Id,
},
{
onSuccess: () =>
notifySuccess(
'Container successfully disconnected',
networkId
),
}
);
}
}}
>

View file

@ -21,7 +21,7 @@ import { useIsSwarm } from '../../proxy/queries/useInfo';
import { useColumns } from './columns';
import { DecoratedNetwork } from './types';
import { NestedNetworksDatatable } from './NestedNetwordsTable';
import { NestedNetworksDatatable } from './NestedNetworksTable';
const storageKey = 'docker.networks';

View file

@ -1,72 +0,0 @@
import { ContainerId } from '@/react/docker/containers/types';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { addNodeHeader } from '../proxy/addNodeHeader';
import { NetworkId, DockerNetwork } from './types';
type NetworkAction = 'connect' | 'disconnect' | 'create';
export async function getNetwork(
environmentId: EnvironmentId,
networkId: NetworkId,
{ nodeName }: { nodeName?: string } = {}
) {
const headers = addNodeHeader(nodeName);
try {
const { data: network } = await axios.get<DockerNetwork>(
buildUrl(environmentId, networkId),
{ headers }
);
return network;
} catch (e) {
throw parseAxiosError(e, 'Unable to retrieve network details');
}
}
export async function deleteNetwork(
environmentId: EnvironmentId,
networkId: NetworkId
) {
try {
await axios.delete(buildUrl(environmentId, networkId));
return networkId;
} catch (e) {
throw parseAxiosError(e, 'Unable to remove network');
}
}
export async function disconnectContainer(
environmentId: EnvironmentId,
networkId: NetworkId,
containerId: ContainerId
) {
try {
await axios.post(buildUrl(environmentId, networkId, 'disconnect'), {
Container: containerId,
Force: false,
});
return { networkId, environmentId };
} catch (e) {
throw parseAxiosError(e, 'Unable to disconnect container from network');
}
}
function buildUrl(
environmentId: EnvironmentId,
networkId?: NetworkId,
action?: NetworkAction
) {
let url = `endpoints/${environmentId}/docker/networks`;
if (networkId) {
url += `/${networkId}`;
}
if (action) {
url += `/${action}`;
}
return url;
}

View file

@ -1,93 +0,0 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { EnvironmentId } from '@/react/portainer/environments/types';
import {
error as notifyError,
success as notifySuccess,
} from '@/portainer/services/notifications';
import { ContainerId } from '@/react/docker/containers/types';
import {
getNetwork,
deleteNetwork,
disconnectContainer,
} from './network.service';
import { NetworkId } from './types';
export function useNetwork(
environmentId: EnvironmentId,
networkId: NetworkId,
{ nodeName }: { nodeName?: string } = {}
) {
return useQuery(
[
'environments',
environmentId,
'docker',
'networks',
networkId,
{ nodeName },
],
() => getNetwork(environmentId, networkId, { nodeName }),
{
onError: (err) => {
notifyError('Failure', err as Error, 'Unable to get network');
},
}
);
}
export function useDeleteNetwork() {
return useMutation(
({
environmentId,
networkId,
}: {
environmentId: EnvironmentId;
networkId: NetworkId;
}) => deleteNetwork(environmentId, networkId),
{
onSuccess: (networkId) => {
notifySuccess('Network successfully removed', networkId);
},
onError: (err) => {
notifyError('Failure', err as Error, 'Unable to remove network');
},
}
);
}
export function useDisconnectContainer() {
const client = useQueryClient();
return useMutation(
({
containerId,
environmentId,
networkId,
}: {
containerId: ContainerId;
environmentId: EnvironmentId;
networkId: NetworkId;
}) => disconnectContainer(environmentId, networkId, containerId),
{
onSuccess: ({ networkId, environmentId }) => {
notifySuccess('Container successfully disconnected', networkId);
return client.invalidateQueries([
'environments',
environmentId,
'docker',
'networks',
networkId,
]);
},
onError: (err) => {
notifyError(
'Failure',
err as Error,
'Unable to disconnect container from network'
);
},
}
);
}

View file

@ -1,20 +0,0 @@
import { EnvironmentId } from '@/react/portainer/environments/types';
import { buildUrl as buildDockerUrl } from '../../proxy/queries/build-url';
import { NetworkId } from '../types';
export function buildUrl(
environmentId: EnvironmentId,
{ id, action }: { id?: NetworkId; action?: string } = {}
) {
let baseUrl = 'networks';
if (id) {
baseUrl += `/${id}`;
}
if (action) {
baseUrl += `/${action}`;
}
return buildDockerUrl(environmentId, baseUrl);
}

View file

@ -1,6 +1,7 @@
import { EnvironmentId } from '@/react/portainer/environments/types';
import { queryKeys as dockerQueryKeys } from '../../queries/utils';
import { NetworkId } from '../types';
import { NetworksQuery } from './types';
@ -9,4 +10,6 @@ export const queryKeys = {
[...dockerQueryKeys.root(environmentId), 'networks'] as const,
list: (environmentId: EnvironmentId, query: NetworksQuery) =>
[...queryKeys.base(environmentId), 'list', query] as const,
item: (environmentId: EnvironmentId, id: NetworkId) =>
[...queryKeys.base(environmentId), id] as const,
};

View file

@ -10,9 +10,8 @@ import {
} from '@/react-tools/react-query';
import { queryKeys as dockerQueryKeys } from '../../queries/utils';
import { addNodeHeader } from '../../proxy/addNodeHeader';
import { buildUrl } from './buildUrl';
import { withAgentTargetHeader } from '../../proxy/queries/utils';
import { buildDockerProxyUrl } from '../../proxy/queries/buildDockerProxyUrl';
interface ConnectContainerPayload {
Container: string;
@ -40,6 +39,9 @@ interface ConnectContainer {
nodeName?: string;
}
/**
* Raw docker API proxy
*/
export async function connectContainer({
environmentId,
containerId,
@ -56,13 +58,11 @@ export async function connectContainer({
};
}
const headers = addNodeHeader(nodeName);
try {
await axios.post(
buildUrl(environmentId, { id: networkId, action: 'connect' }),
buildDockerProxyUrl(environmentId, 'networks', networkId, 'connect'),
payload,
{ headers }
{ headers: { ...withAgentTargetHeader(nodeName) } }
);
} catch (err) {
throw parseAxiosError(err, 'Unable to connect container');

View file

@ -0,0 +1,91 @@
import { Network } from 'docker-types/generated/1.41';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { buildDockerProxyUrl } from '../../proxy/queries/buildDockerProxyUrl';
import {
withAgentManagerOperationHeader,
withAgentTargetHeader,
} from '../../proxy/queries/utils';
type MacvlanConfigOnly = {
ConfigOnly: true;
Internal: false;
Attachable: false;
Options: {
parent: string; // parent network card
};
};
type MacvlanConfigFrom = {
ConfigFrom: {
Network: string;
};
Scope: 'swarm' | 'local';
};
type NetworkConfigBase = {
Name: Required<Network>['Name'];
CheckDuplicate?: boolean;
Driver?: string;
Internal?: boolean;
Attachable?: boolean;
Ingress?: boolean;
IPAM?: Network['IPAM'];
EnableIPv6?: boolean;
Options?: Network['Options'];
Labels?: Network['Labels'];
};
/**
* This type definition of NetworkConfig doesnt enforce the usage of only one type of the union
* and not a mix of fields of the unionised types.
* e.g. the following is valid for TS while it is not for the Docker API
*
* const config: NetworkConfig = {
* Name: 'my-network', // shared
* ConfigOnly: true, // MacvlanConfigOnly
* Scope: 'swarm', // MacvlanConfigFrom
* }
*
*/
type NetworkConfig =
| NetworkConfigBase
| (NetworkConfigBase & MacvlanConfigOnly)
| (NetworkConfigBase & MacvlanConfigFrom);
type CreateOptions = {
nodeName?: string;
agentManagerOperation?: boolean;
};
type CreateNetworkResponse = {
Id: string;
Warning: string;
};
/**
* Raw docker API proxy
*/
export async function createNetwork(
environmentId: EnvironmentId,
networkConfig: NetworkConfig,
{ nodeName, agentManagerOperation }: CreateOptions = {}
) {
try {
const { data } = await axios.post<CreateNetworkResponse>(
buildDockerProxyUrl(environmentId, 'networks', 'create'),
networkConfig,
{
headers: {
...withAgentTargetHeader(nodeName),
...withAgentManagerOperationHeader(agentManagerOperation),
},
}
);
return data;
} catch (err) {
throw parseAxiosError(err, 'Unable to create network');
}
}

View file

@ -0,0 +1,47 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import {
mutationOptions,
withError,
withInvalidate,
} from '@/react-tools/react-query';
import { EnvironmentId } from '@/react/portainer/environments/types';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { buildDockerProxyUrl } from '../../proxy/queries/buildDockerProxyUrl';
import { NetworkId } from '../types';
import { queryKeys } from './queryKeys';
export function useDeleteNetwork(environmentId: EnvironmentId) {
const queryClient = useQueryClient();
return useMutation(
({ networkId }: { networkId: NetworkId }) =>
deleteNetwork(environmentId, networkId),
mutationOptions(
withInvalidate(queryClient, [queryKeys.base(environmentId)]),
withError('Unable to remove network')
)
);
}
/**
* Raw docker API proxy
* @param environmentId
* @param networkId
* @returns
*/
export async function deleteNetwork(
environmentId: EnvironmentId,
networkId: NetworkId
) {
try {
await axios.delete(
buildDockerProxyUrl(environmentId, 'networks', networkId)
);
return networkId;
} catch (err) {
throw parseAxiosError(err, 'Unable to remove network');
}
}

View file

@ -0,0 +1,60 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { EnvironmentId } from '@/react/portainer/environments/types';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import {
mutationOptions,
withError,
withInvalidate,
} from '@/react-tools/react-query';
import { buildDockerProxyUrl } from '../../proxy/queries/buildDockerProxyUrl';
import { ContainerId } from '../../containers/types';
import { NetworkId } from '../types';
import { queryKeys } from './queryKeys';
export function useDisconnectContainer({
environmentId,
networkId,
}: {
environmentId: EnvironmentId;
networkId: NetworkId;
}) {
const client = useQueryClient();
return useMutation(
({ containerId }: { containerId: ContainerId }) =>
disconnectContainer(environmentId, networkId, containerId),
mutationOptions(
withInvalidate(client, [queryKeys.item(environmentId, networkId)]),
withError('Unable to disconnect container from network')
)
);
}
/**
* Raw docker API proxy
* @param environmentId
* @param networkId
* @param containerId
* @returns
*/
export async function disconnectContainer(
environmentId: EnvironmentId,
networkId: NetworkId,
containerId: ContainerId
) {
try {
await axios.post(
buildDockerProxyUrl(environmentId, 'networks', networkId, 'disconnect'),
{
Container: containerId,
Force: false,
}
);
return { networkId, environmentId };
} catch (err) {
throw parseAxiosError(err, 'Unable to disconnect container from network');
}
}

View file

@ -0,0 +1,52 @@
import { useQuery } from '@tanstack/react-query';
import { withGlobalError } from '@/react-tools/react-query';
import { EnvironmentId } from '@/react/portainer/environments/types';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { DockerNetwork, NetworkId } from '../types';
import { buildDockerProxyUrl } from '../../proxy/queries/buildDockerProxyUrl';
import { withAgentTargetHeader } from '../../proxy/queries/utils';
import { queryKeys } from './queryKeys';
export function useNetwork(
environmentId: EnvironmentId,
networkId: NetworkId,
{ nodeName }: { nodeName?: string } = {}
) {
return useQuery(
[...queryKeys.item(environmentId, networkId), { nodeName }],
() => getNetwork(environmentId, networkId, { nodeName }),
{
...withGlobalError('Unable to get network'),
}
);
}
/**
* Raw docker API proxy
* @param environmentId
* @param networkId
* @param param2
* @returns
*/
export async function getNetwork(
environmentId: EnvironmentId,
networkId: NetworkId,
{ nodeName }: { nodeName?: string } = {}
) {
try {
const { data: network } = await axios.get<DockerNetwork>(
buildDockerProxyUrl(environmentId, 'networks', networkId),
{
headers: {
...withAgentTargetHeader(nodeName),
},
}
);
return network;
} catch (e) {
throw parseAxiosError(e as Error, 'Unable to retrieve network details');
}
}

View file

@ -3,8 +3,9 @@ import { useQuery } from '@tanstack/react-query';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { buildUrl } from '../../proxy/queries/build-url';
import { DockerNetwork } from '../types';
import { withFiltersQueryParam } from '../../proxy/queries/utils';
import { buildDockerProxyUrl } from '../../proxy/queries/buildDockerProxyUrl';
import { queryKeys } from './queryKeys';
import { NetworksQuery } from './types';
@ -29,17 +30,18 @@ export function useNetworks<T = Array<DockerNetwork>>(
);
}
/**
* Raw docker API proxy
*/
export async function getNetworks(
environmentId: EnvironmentId,
{ local, swarm, swarmAttachable, filters }: NetworksQuery
) {
try {
const { data } = await axios.get<Array<DockerNetwork>>(
buildUrl(environmentId, 'networks'),
filters && {
params: {
filters,
},
buildDockerProxyUrl(environmentId, 'networks'),
{
params: { ...withFiltersQueryParam(filters) },
}
);

View file

@ -1,6 +1,5 @@
import { PortainerMetadata } from '@/react/docker/types';
import { ContainerId } from '../containers/types';
import { PortainerResponse } from '@/react/docker/types';
import { ContainerId } from '@/react/docker/containers/types';
export type IPConfig = {
Subnet: string;
@ -32,7 +31,7 @@ export type NetworkResponseContainers = Record<
NetworkResponseContainer
>;
export interface DockerNetwork {
export interface DockerNetwork extends PortainerResponse<unknown> {
Name: string;
Id: NetworkId;
Driver: string;
@ -44,7 +43,6 @@ export interface DockerNetwork {
Driver: string;
Options: IpamOptions;
};
Portainer?: PortainerMetadata;
Options: NetworkOptions;
Containers: NetworkResponseContainers;
}