1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-23 07:19:41 +02:00

feat(podman): support add podman envs in the wizard [r8s-20] (#12056)
Some checks failed
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Has been cancelled
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Has been cancelled
ci / build_images (map[arch:arm platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Has been cancelled
/ triage (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Test / test-client (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:linux]) (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Has been cancelled
Test / test-server (map[arch:arm64 platform:linux]) (push) Has been cancelled
ci / build_manifests (push) Has been cancelled

This commit is contained in:
Ali 2024-09-25 11:55:07 +12:00 committed by GitHub
parent db616bc8a5
commit 32e94d4e4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
108 changed files with 1921 additions and 272 deletions

View file

@ -39,7 +39,7 @@ export function PortsMappingField({
label="Port mapping"
value={value}
onChange={onChange}
addLabel="map additional port"
addLabel="Map additional port"
itemBuilder={() => ({
hostPort: '',
containerPort: '',
@ -79,7 +79,7 @@ function Item({
readOnly={readOnly}
value={item.hostPort}
onChange={(e) => handleChange('hostPort', e.target.value)}
label="host"
label="Host"
placeholder="e.g. 80"
className="w-1/2"
id={`hostPort-${index}`}
@ -95,7 +95,7 @@ function Item({
readOnly={readOnly}
value={item.containerPort}
onChange={(e) => handleChange('containerPort', e.target.value)}
label="container"
label="Container"
placeholder="e.g. 80"
className="w-1/2"
id={`containerPort-${index}`}
@ -105,7 +105,10 @@ function Item({
<ButtonSelector<Protocol>
onChange={(value) => handleChange('protocol', value)}
value={item.protocol}
options={[{ value: 'tcp' }, { value: 'udp' }]}
options={[
{ value: 'tcp', label: 'TCP' },
{ value: 'udp', label: 'UDP' },
]}
disabled={disabled}
readOnly={readOnly}
/>

View file

@ -2,8 +2,9 @@ import { FormikErrors } from 'formik';
import { array, object, SchemaOf, string } from 'yup';
import _ from 'lodash';
import { useLoggingPlugins } from '@/react/docker/proxy/queries/useServicePlugins';
import { useLoggingPlugins } from '@/react/docker/proxy/queries/usePlugins';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { useIsPodman } from '@/react/portainer/environments/queries/useIsPodman';
import { FormControl } from '@@/form-components/FormControl';
import { FormSection } from '@@/form-components/FormSection';
@ -30,8 +31,9 @@ export function LoggerConfig({
errors?: FormikErrors<LogConfig>;
}) {
const envId = useEnvironmentId();
const pluginsQuery = useLoggingPlugins(envId, apiVersion < 1.25);
const isPodman = useIsPodman(envId);
const isSystem = apiVersion < 1.25;
const pluginsQuery = useLoggingPlugins(envId, isSystem, isPodman);
if (!pluginsQuery.data) {
return null;

View file

@ -1,5 +1,8 @@
import { FormikErrors } from 'formik';
import { useIsPodman } from '@/react/portainer/environments/queries/useIsPodman';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { FormControl } from '@@/form-components/FormControl';
import { Input } from '@@/form-components/Input';
@ -19,12 +22,15 @@ export function NetworkTab({
setFieldValue: (field: string, value: unknown) => void;
errors?: FormikErrors<Values>;
}) {
const envId = useEnvironmentId();
const isPodman = useIsPodman(envId);
const additionalOptions = getAdditionalOptions(isPodman);
return (
<div className="mt-3">
<FormControl label="Network" errors={errors?.networkMode}>
<NetworkSelector
value={values.networkMode}
additionalOptions={[{ label: 'Container', value: CONTAINER_MODE }]}
additionalOptions={additionalOptions}
onChange={(networkMode) => setFieldValue('networkMode', networkMode)}
/>
</FormControl>
@ -105,3 +111,10 @@ export function NetworkTab({
</div>
);
}
function getAdditionalOptions(isPodman?: boolean) {
if (isPodman) {
return [];
}
return [{ label: 'Container', value: CONTAINER_MODE }];
}

View file

@ -0,0 +1,147 @@
import { describe, it, expect } from 'vitest';
import { DockerNetwork } from '@/react/docker/networks/types';
import { ContainerListViewModel } from '../../types';
import { ContainerDetailsJSON } from '../../queries/useContainer';
import { getDefaultViewModel, getNetworkMode } from './toViewModel';
describe('getDefaultViewModel', () => {
it('should return the correct default view model for Windows', () => {
const result = getDefaultViewModel(true);
expect(result).toEqual({
networkMode: 'nat',
hostname: '',
domain: '',
macAddress: '',
ipv4Address: '',
ipv6Address: '',
primaryDns: '',
secondaryDns: '',
hostsFileEntries: [],
container: '',
});
});
it('should return the correct default view model for Podman', () => {
const result = getDefaultViewModel(false, true);
expect(result).toEqual({
networkMode: 'podman',
hostname: '',
domain: '',
macAddress: '',
ipv4Address: '',
ipv6Address: '',
primaryDns: '',
secondaryDns: '',
hostsFileEntries: [],
container: '',
});
});
it('should return the correct default view model for Linux Docker', () => {
const result = getDefaultViewModel(false);
expect(result).toEqual({
networkMode: 'bridge',
hostname: '',
domain: '',
macAddress: '',
ipv4Address: '',
ipv6Address: '',
primaryDns: '',
secondaryDns: '',
hostsFileEntries: [],
container: '',
});
});
});
describe('getNetworkMode', () => {
const mockNetworks: Array<DockerNetwork> = [
{
Name: 'bridge',
Id: 'bridge-id',
Driver: 'bridge',
Scope: 'local',
Attachable: false,
Internal: false,
IPAM: { Config: [], Driver: '', Options: {} },
Options: {},
Containers: {},
},
{
Name: 'host',
Id: 'host-id',
Driver: 'host',
Scope: 'local',
Attachable: false,
Internal: false,
IPAM: { Config: [], Driver: '', Options: {} },
Options: {},
Containers: {},
},
{
Name: 'custom',
Id: 'custom-id',
Driver: 'bridge',
Scope: 'local',
Attachable: true,
Internal: false,
IPAM: { Config: [], Driver: '', Options: {} },
Options: {},
Containers: {},
},
];
const mockRunningContainers: Array<ContainerListViewModel> = [
{
Id: 'container-1',
Names: ['container-1-name'],
} as ContainerListViewModel, // gaslight the type to avoid over-specifying
];
it('should return the network mode from HostConfig', () => {
const config: ContainerDetailsJSON = {
HostConfig: { NetworkMode: 'host' },
};
expect(getNetworkMode(config, mockNetworks)).toEqual(['host']);
});
it('should return the network mode from NetworkSettings if HostConfig is empty', () => {
const config: ContainerDetailsJSON = {
NetworkSettings: { Networks: { custom: {} } },
};
expect(getNetworkMode(config, mockNetworks)).toEqual(['custom']);
});
it('should return container mode when NetworkMode starts with "container:"', () => {
const config: ContainerDetailsJSON = {
HostConfig: { NetworkMode: 'container:container-1' },
};
expect(getNetworkMode(config, mockNetworks, mockRunningContainers)).toEqual(
['container', 'container-1-name']
);
});
it('should return "podman" for bridge network when isPodman is true', () => {
const config: ContainerDetailsJSON = {
HostConfig: { NetworkMode: 'bridge' },
};
expect(getNetworkMode(config, mockNetworks, [], true)).toEqual(['podman']);
});
it('should return "bridge" for default network mode on Docker', () => {
const config: ContainerDetailsJSON = {
HostConfig: { NetworkMode: 'default' },
};
expect(getNetworkMode(config, mockNetworks)).toEqual(['bridge']);
});
it('should return the first available network if no matching network is found', () => {
const config: ContainerDetailsJSON = {
HostConfig: { NetworkMode: 'non-existent' },
};
expect(getNetworkMode(config, mockNetworks)).toEqual(['bridge']);
});
});

View file

@ -5,8 +5,8 @@ import { ContainerListViewModel } from '../../types';
import { CONTAINER_MODE, Values } from './types';
export function getDefaultViewModel(isWindows: boolean) {
const networkMode = isWindows ? 'nat' : 'bridge';
export function getDefaultViewModel(isWindows: boolean, isPodman?: boolean) {
const networkMode = getDefaultNetworkMode(isWindows, isPodman);
return {
networkMode,
hostname: '',
@ -21,10 +21,17 @@ export function getDefaultViewModel(isWindows: boolean) {
};
}
export function getDefaultNetworkMode(isWindows: boolean, isPodman?: boolean) {
if (isWindows) return 'nat';
if (isPodman) return 'podman';
return 'bridge';
}
export function toViewModel(
config: ContainerDetailsJSON,
networks: Array<DockerNetwork>,
runningContainers: Array<ContainerListViewModel> = []
runningContainers: Array<ContainerListViewModel> = [],
isPodman?: boolean
): Values {
const dns = config.HostConfig?.Dns;
const [primaryDns = '', secondaryDns = ''] = dns || [];
@ -34,7 +41,8 @@ export function toViewModel(
const [networkMode, container = ''] = getNetworkMode(
config,
networks,
runningContainers
runningContainers,
isPodman
);
const networkSettings = config.NetworkSettings?.Networks?.[networkMode];
@ -61,10 +69,11 @@ export function toViewModel(
};
}
function getNetworkMode(
export function getNetworkMode(
config: ContainerDetailsJSON,
networks: Array<DockerNetwork>,
runningContainers: Array<ContainerListViewModel> = []
runningContainers: Array<ContainerListViewModel> = [],
isPodman?: boolean
) {
let networkMode = config.HostConfig?.NetworkMode || '';
if (!networkMode) {
@ -85,6 +94,9 @@ function getNetworkMode(
const networkNames = networks.map((n) => n.Name);
if (networkNames.includes(networkMode)) {
if (isPodman && networkMode === 'bridge') {
return ['podman'] as const;
}
return [networkMode] as const;
}
@ -92,6 +104,9 @@ function getNetworkMode(
networkNames.includes('bridge') &&
(!networkMode || networkMode === 'default' || networkMode === 'bridge')
) {
if (isPodman) {
return ['podman'] as const;
}
return ['bridge'] as const;
}

View file

@ -1,5 +1,6 @@
import { useCurrentStateAndParams } from '@uirouter/react';
import { useIsPodman } from '@/react/portainer/environments/queries/useIsPodman';
import {
BaseFormValues,
baseFormUtils,
@ -46,6 +47,8 @@ import { useNetworksForSelector } from '../components/NetworkSelector';
import { useContainers } from '../queries/useContainers';
import { useContainer } from '../queries/useContainer';
import { getDefaultNetworkMode } from './NetworkTab/toViewModel';
export interface Values extends BaseFormValues {
commands: CommandsTabValues;
volumes: VolumesTabValues;
@ -80,6 +83,7 @@ export function useInitialValues(submitting: boolean, isWindows: boolean) {
const registriesQuery = useEnvironmentRegistries(environmentId, {
enabled: !!from,
});
const isPodman = useIsPodman(environmentId);
if (!networksQuery.data) {
return null;
@ -87,7 +91,13 @@ export function useInitialValues(submitting: boolean, isWindows: boolean) {
if (!from) {
return {
initialValues: defaultValues(isPureAdmin, user.Id, nodeName, isWindows),
initialValues: defaultValues(
isPureAdmin,
user.Id,
nodeName,
isWindows,
isPodman
),
};
}
@ -110,7 +120,11 @@ export function useInitialValues(submitting: boolean, isWindows: boolean) {
const extraNetworks = Object.entries(
fromContainer.NetworkSettings?.Networks || {}
)
.filter(([n]) => n !== network.networkMode)
.filter(
([n]) =>
n !== network.networkMode &&
n !== getDefaultNetworkMode(isWindows, isPodman)
)
.map(([networkName, network]) => ({
networkName,
aliases: (network.Aliases || []).filter(
@ -129,7 +143,8 @@ export function useInitialValues(submitting: boolean, isWindows: boolean) {
network: networkTabUtils.toViewModel(
fromContainer,
networksQuery.data,
runningContainersQuery.data
runningContainersQuery.data,
isPodman
),
labels: labelsTabUtils.toViewModel(fromContainer),
restartPolicy: restartPolicyTabUtils.toViewModel(fromContainer),
@ -153,12 +168,13 @@ function defaultValues(
isPureAdmin: boolean,
currentUserId: UserId,
nodeName: string,
isWindows: boolean
isWindows: boolean,
isPodman?: boolean
): Values {
return {
commands: commandsTabUtils.getDefaultViewModel(),
volumes: volumesTabUtils.getDefaultViewModel(),
network: networkTabUtils.getDefaultViewModel(isWindows), // windows containers should default to the nat network, not the bridge
network: networkTabUtils.getDefaultViewModel(isWindows, isPodman), // windows containers should default to the nat network, not the bridge
labels: labelsTabUtils.getDefaultViewModel(),
restartPolicy: restartPolicyTabUtils.getDefaultViewModel(),
resources: resourcesTabUtils.getDefaultViewModel(),