mirror of
https://github.com/portainer/portainer.git
synced 2025-07-20 05:49:40 +02:00
refactor(containers): migrate create view to react [EE-2307] (#9175)
This commit is contained in:
parent
bc0050a7b4
commit
d970f0e2bc
71 changed files with 2612 additions and 1399 deletions
355
app/react/docker/containers/CreateView/useCreateMutation.tsx
Normal file
355
app/react/docker/containers/CreateView/useCreateMutation.tsx
Normal file
|
@ -0,0 +1,355 @@
|
|||
import { useMutation, useQueryClient } from 'react-query';
|
||||
import { AxiosRequestHeaders } from 'axios';
|
||||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import {
|
||||
Environment,
|
||||
EnvironmentId,
|
||||
EnvironmentType,
|
||||
} from '@/react/portainer/environments/types';
|
||||
import {
|
||||
Registry,
|
||||
RegistryId,
|
||||
} from '@/react/portainer/registries/types/registry';
|
||||
import { createWebhook } from '@/react/portainer/webhooks/createWebhook';
|
||||
import { WebhookType } from '@/react/portainer/webhooks/types';
|
||||
import {
|
||||
AccessControlFormData,
|
||||
ResourceControlResponse,
|
||||
} from '@/react/portainer/access-control/types';
|
||||
import { applyResourceControl } from '@/react/portainer/access-control/access-control.service';
|
||||
import PortainerError from '@/portainer/error';
|
||||
import {
|
||||
mutationOptions,
|
||||
withError,
|
||||
withInvalidate,
|
||||
} from '@/react-tools/react-query';
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
|
||||
import { pullImage } from '../../images/queries/usePullImageMutation';
|
||||
import {
|
||||
removeContainer,
|
||||
renameContainer,
|
||||
startContainer,
|
||||
stopContainer,
|
||||
urlBuilder,
|
||||
} from '../containers.service';
|
||||
import { PortainerResponse } from '../../types';
|
||||
import { connectContainer } from '../../networks/queries/useConnectContainer';
|
||||
import { DockerContainer } from '../types';
|
||||
import { queryKeys } from '../queries/query-keys';
|
||||
|
||||
import { CreateContainerRequest } from './types';
|
||||
import { Values } from './useInitialValues';
|
||||
|
||||
interface ExtraNetwork {
|
||||
networkName: string;
|
||||
aliases: string[];
|
||||
}
|
||||
|
||||
export function useCreateOrReplaceMutation() {
|
||||
const environmentId = useEnvironmentId();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation(
|
||||
createOrReplace,
|
||||
mutationOptions(
|
||||
withError('Failed to create container'),
|
||||
withInvalidate(queryClient, [queryKeys.list(environmentId)])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
interface CreateOptions {
|
||||
config: CreateContainerRequest;
|
||||
values: Values;
|
||||
registry?: Registry;
|
||||
environment: Environment;
|
||||
}
|
||||
|
||||
interface ReplaceOptions extends CreateOptions {
|
||||
oldContainer: DockerContainer;
|
||||
extraNetworks: Array<ExtraNetwork>;
|
||||
}
|
||||
|
||||
function isReplace(
|
||||
options: ReplaceOptions | CreateOptions
|
||||
): options is ReplaceOptions {
|
||||
return 'oldContainer' in options && !!options.oldContainer;
|
||||
}
|
||||
|
||||
export function createOrReplace(options: ReplaceOptions | CreateOptions) {
|
||||
return isReplace(options) ? replace(options) : create(options);
|
||||
}
|
||||
|
||||
async function create({
|
||||
config,
|
||||
values,
|
||||
registry,
|
||||
environment,
|
||||
}: CreateOptions) {
|
||||
await pullImageIfNeeded(
|
||||
environment.Id,
|
||||
values.nodeName,
|
||||
values.alwaysPull,
|
||||
values.image.image,
|
||||
registry
|
||||
);
|
||||
|
||||
const containerResponse = await createAndStart(
|
||||
environment,
|
||||
config,
|
||||
values.name,
|
||||
values.nodeName
|
||||
);
|
||||
|
||||
await applyContainerSettings(
|
||||
containerResponse.Id,
|
||||
environment,
|
||||
values.enableWebhook,
|
||||
values.accessControl,
|
||||
containerResponse.Portainer?.ResourceControl,
|
||||
registry
|
||||
);
|
||||
}
|
||||
|
||||
async function replace({
|
||||
oldContainer,
|
||||
config,
|
||||
values,
|
||||
registry,
|
||||
environment,
|
||||
extraNetworks,
|
||||
}: ReplaceOptions) {
|
||||
await pullImageIfNeeded(
|
||||
environment.Id,
|
||||
values.nodeName,
|
||||
values.alwaysPull,
|
||||
values.image.image,
|
||||
registry
|
||||
);
|
||||
|
||||
const containerResponse = await renameAndCreate(
|
||||
environment,
|
||||
values,
|
||||
oldContainer,
|
||||
config
|
||||
);
|
||||
|
||||
await applyContainerSettings(
|
||||
containerResponse.Id,
|
||||
environment,
|
||||
values.enableWebhook,
|
||||
values.accessControl,
|
||||
containerResponse.Portainer?.ResourceControl,
|
||||
registry
|
||||
);
|
||||
|
||||
await connectToExtraNetworks(
|
||||
environment.Id,
|
||||
values.nodeName,
|
||||
containerResponse.Id,
|
||||
extraNetworks
|
||||
);
|
||||
|
||||
await removeContainer(environment.Id, oldContainer.Id, {
|
||||
nodeName: values.nodeName,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* stop and renames the old container, and creates and stops the new container.
|
||||
* on any failure, it will rename the old container to its original name
|
||||
*/
|
||||
async function renameAndCreate(
|
||||
environment: Environment,
|
||||
values: Values,
|
||||
oldContainer: DockerContainer,
|
||||
config: CreateContainerRequest
|
||||
) {
|
||||
let renamed = false;
|
||||
try {
|
||||
await stopContainerIfNeeded(environment.Id, values.nodeName, oldContainer);
|
||||
|
||||
await renameContainer(
|
||||
environment.Id,
|
||||
oldContainer.Id,
|
||||
`${oldContainer.Names[0]}-old`,
|
||||
{ nodeName: values.nodeName }
|
||||
);
|
||||
renamed = true;
|
||||
|
||||
return await createAndStart(
|
||||
environment,
|
||||
config,
|
||||
values.name,
|
||||
values.nodeName
|
||||
);
|
||||
} catch (e) {
|
||||
if (renamed) {
|
||||
await renameContainer(environment.Id, oldContainer.Id, values.name, {
|
||||
nodeName: values.nodeName,
|
||||
});
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a webhook if necessary and applies resource control
|
||||
*/
|
||||
async function applyContainerSettings(
|
||||
containerId: string,
|
||||
environment: Environment,
|
||||
enableWebhook: boolean,
|
||||
accessControl: AccessControlFormData,
|
||||
resourceControl?: ResourceControlResponse,
|
||||
registry?: Registry
|
||||
) {
|
||||
if (enableWebhook) {
|
||||
await createContainerWebhook(containerId, environment, registry?.Id);
|
||||
}
|
||||
|
||||
// Portainer will always return a resource control, but since types mark it as optional, we need to check it.
|
||||
// Ignoring the missing value will result with bugs, hence it's better to throw an error
|
||||
if (!resourceControl) {
|
||||
throw new PortainerError('resource control expected after creation');
|
||||
}
|
||||
|
||||
await applyResourceControl(accessControl, resourceControl.Id);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a new container and starts it.
|
||||
* on failure, it will remove the new container
|
||||
*/
|
||||
async function createAndStart(
|
||||
environment: Environment,
|
||||
config: CreateContainerRequest,
|
||||
name: string,
|
||||
nodeName: string
|
||||
) {
|
||||
let containerId = '';
|
||||
try {
|
||||
const containerResponse = await createContainer(
|
||||
environment.Id,
|
||||
config,
|
||||
name,
|
||||
{
|
||||
nodeName,
|
||||
}
|
||||
);
|
||||
|
||||
containerId = containerResponse.Id;
|
||||
|
||||
await startContainer(environment.Id, containerResponse.Id, { nodeName });
|
||||
return containerResponse;
|
||||
} catch (e) {
|
||||
if (containerId) {
|
||||
await removeContainer(environment.Id, containerId, {
|
||||
nodeName,
|
||||
});
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async function pullImageIfNeeded(
|
||||
environmentId: EnvironmentId,
|
||||
nodeName: string,
|
||||
pull: boolean,
|
||||
image: string,
|
||||
registry?: Registry
|
||||
) {
|
||||
if (!pull) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return pullImage({
|
||||
environmentId,
|
||||
nodeName,
|
||||
image,
|
||||
registry,
|
||||
ignoreErrors: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function createContainer(
|
||||
environmentId: EnvironmentId,
|
||||
config: CreateContainerRequest,
|
||||
name?: string,
|
||||
{ nodeName }: { nodeName?: string } = {}
|
||||
) {
|
||||
try {
|
||||
const headers: AxiosRequestHeaders = {};
|
||||
|
||||
if (nodeName) {
|
||||
headers['X-PortainerAgent-Target'] = nodeName;
|
||||
}
|
||||
|
||||
const { data } = await axios.post<
|
||||
PortainerResponse<{ Id: string; Warnings: Array<string> }>
|
||||
>(urlBuilder(environmentId, undefined, 'create'), config, {
|
||||
headers,
|
||||
params: { name },
|
||||
});
|
||||
|
||||
return data;
|
||||
} catch (err) {
|
||||
throw parseAxiosError(err, 'Unable to create container');
|
||||
}
|
||||
}
|
||||
|
||||
async function createContainerWebhook(
|
||||
containerId: string,
|
||||
environment: Environment,
|
||||
registryId?: RegistryId
|
||||
) {
|
||||
const isNotEdgeAgentOnDockerEnvironment =
|
||||
environment.Type !== EnvironmentType.EdgeAgentOnDocker;
|
||||
if (!isNotEdgeAgentOnDockerEnvironment) {
|
||||
return;
|
||||
}
|
||||
|
||||
await createWebhook({
|
||||
resourceId: containerId,
|
||||
environmentId: environment.Id,
|
||||
registryId,
|
||||
webhookType: WebhookType.DockerContainer,
|
||||
});
|
||||
}
|
||||
|
||||
function connectToExtraNetworks(
|
||||
environmentId: EnvironmentId,
|
||||
nodeName: string,
|
||||
containerId: string,
|
||||
extraNetworks: Array<ExtraNetwork>
|
||||
) {
|
||||
if (!extraNetworks) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
extraNetworks.map(({ networkName, aliases }) =>
|
||||
connectContainer({
|
||||
networkId: networkName,
|
||||
nodeName,
|
||||
containerId,
|
||||
environmentId,
|
||||
aliases,
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function stopContainerIfNeeded(
|
||||
environmentId: EnvironmentId,
|
||||
nodeName: string,
|
||||
container: DockerContainer
|
||||
) {
|
||||
if (container.State !== 'running' || !container.Id) {
|
||||
return null;
|
||||
}
|
||||
return stopContainer(environmentId, container.Id, { nodeName });
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue