mirror of
https://github.com/portainer/portainer.git
synced 2025-07-19 05:19:39 +02:00
fix(app/kubernetes): Fix listing of secrets and configmaps with same name [r8s-288] (#897)
This commit is contained in:
parent
383bcc4113
commit
4e4c5ffdb6
4 changed files with 1316 additions and 25 deletions
|
@ -0,0 +1,702 @@
|
|||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { vi, beforeEach, afterEach } from 'vitest';
|
||||
|
||||
import { Application } from '../../types';
|
||||
|
||||
import { ApplicationEnvVarsTable } from './ApplicationEnvVarsTable';
|
||||
|
||||
// Mock icon components
|
||||
vi.mock('lucide-react', () => ({
|
||||
Asterisk: () => <span data-cy="asterisk-icon" />,
|
||||
File: () => <span data-cy="file-icon" />,
|
||||
FileCode: () => <span data-cy="file-code-icon" />,
|
||||
Key: () => <span data-cy="key-icon" />,
|
||||
Lock: () => <span data-cy="lock-icon" />,
|
||||
}));
|
||||
|
||||
// Mock UI components
|
||||
vi.mock('@@/Icon', () => ({
|
||||
Icon: ({
|
||||
icon: IconComponent,
|
||||
...props
|
||||
}: {
|
||||
icon: React.ComponentType;
|
||||
[key: string]: unknown;
|
||||
}) => <IconComponent {...props} />,
|
||||
}));
|
||||
|
||||
vi.mock('@@/Tip/TextTip', () => ({
|
||||
TextTip: ({
|
||||
children,
|
||||
color,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
color?: string;
|
||||
}) => (
|
||||
<div data-cy="text-tip" data-color={color}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock the Link component to capture routing props
|
||||
const mockLink = vi.fn();
|
||||
vi.mock('@@/Link', () => ({
|
||||
Link: ({
|
||||
children,
|
||||
to,
|
||||
params,
|
||||
'data-cy': dataCy,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
to: string;
|
||||
params: Record<string, string>;
|
||||
'data-cy'?: string;
|
||||
className?: string;
|
||||
}) => {
|
||||
mockLink({ children, to, params, 'data-cy': dataCy, className });
|
||||
return (
|
||||
<span
|
||||
data-cy={dataCy}
|
||||
data-testid={dataCy}
|
||||
role="link"
|
||||
data-to={to}
|
||||
data-params={JSON.stringify(params)}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
describe('ApplicationEnvVarsTable', () => {
|
||||
beforeEach(() => {
|
||||
mockLink.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render helpful tip when there are no environment variables', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationEnvVarsTable namespace="default" app={app} />);
|
||||
|
||||
expect(
|
||||
screen.getByText('Environment variables, ConfigMaps or Secrets')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('text-tip')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'This application is not using any environment variable, ConfigMap or Secret.'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render nothing when app is undefined', () => {
|
||||
render(<ApplicationEnvVarsTable namespace="default" app={undefined} />);
|
||||
|
||||
expect(
|
||||
screen.getByText('Environment variables, ConfigMaps or Secrets')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('text-tip')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render regular environment variables with direct values', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
env: [
|
||||
{
|
||||
name: 'ENV_VAR',
|
||||
value: 'test-value',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationEnvVarsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('test-container')).toBeInTheDocument();
|
||||
expect(screen.getByText('ENV_VAR')).toBeInTheDocument();
|
||||
expect(screen.getByText('test-value')).toBeInTheDocument();
|
||||
expect(screen.getByText('-')).toBeInTheDocument(); // No configuration resource
|
||||
});
|
||||
|
||||
it('should render configmap environment variables with correct routing', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
env: [
|
||||
{
|
||||
name: 'CONFIG_VAR',
|
||||
valueFrom: {
|
||||
configMapKeyRef: {
|
||||
name: 'test-configmap',
|
||||
key: 'config-key',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationEnvVarsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('test-container')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('CONFIG_VAR')).toHaveLength(2); // Appears in name and value columns
|
||||
// Note: config-key is not displayed in UI - the component shows the env var name
|
||||
expect(screen.getByText('test-configmap')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('key-icon')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('file-code-icon')).toBeInTheDocument();
|
||||
|
||||
// Verify the Link component was called with correct routing parameters
|
||||
expect(mockLink).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'kubernetes.configmaps.configmap',
|
||||
params: {
|
||||
name: 'test-configmap',
|
||||
namespace: 'default',
|
||||
},
|
||||
'data-cy': 'configmap-link-test-configmap',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should render secret environment variables with correct routing', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
env: [
|
||||
{
|
||||
name: 'SECRET_VAR',
|
||||
valueFrom: {
|
||||
secretKeyRef: {
|
||||
name: 'test-secret',
|
||||
key: 'secret-key',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationEnvVarsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('test-container')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('SECRET_VAR')).toHaveLength(2); // Appears in name and value columns
|
||||
// Note: secret-key is not displayed in UI - the component shows the env var name
|
||||
expect(screen.getByText('test-secret')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('key-icon')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('lock-icon')).toBeInTheDocument();
|
||||
|
||||
// Verify the Link component was called with correct routing parameters for secret
|
||||
expect(mockLink).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'kubernetes.secrets.secret',
|
||||
params: {
|
||||
name: 'test-secret',
|
||||
namespace: 'default',
|
||||
},
|
||||
'data-cy': 'configmap-link-test-secret',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should render downward API field references', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
env: [
|
||||
{
|
||||
name: 'POD_NAME',
|
||||
valueFrom: {
|
||||
fieldRef: {
|
||||
fieldPath: 'metadata.name',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationEnvVarsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('test-container')).toBeInTheDocument();
|
||||
expect(screen.getByText('POD_NAME')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
(content, element) =>
|
||||
content.includes('metadata.name') && element?.tagName === 'SPAN'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('downward API')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('asterisk-icon')).toBeInTheDocument();
|
||||
expect(screen.getByText('-')).toBeInTheDocument(); // No configuration resource
|
||||
});
|
||||
|
||||
it('should render envFrom configmap (entire configmap import)', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
envFrom: [
|
||||
{
|
||||
configMapRef: {
|
||||
name: 'entire-configmap',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationEnvVarsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('test-container')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('-')).toHaveLength(2); // EnvFrom doesn't have a specific key name or value
|
||||
expect(screen.getByText('entire-configmap')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('file-code-icon')).toBeInTheDocument();
|
||||
|
||||
// Verify configmap routing
|
||||
expect(mockLink).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'kubernetes.configmaps.configmap',
|
||||
params: {
|
||||
name: 'entire-configmap',
|
||||
namespace: 'default',
|
||||
},
|
||||
'data-cy': 'configmap-link-entire-configmap',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should render envFrom secret (entire secret import)', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
envFrom: [
|
||||
{
|
||||
secretRef: {
|
||||
name: 'entire-secret',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationEnvVarsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('test-container')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('-')).toHaveLength(2); // EnvFrom doesn't have a specific key name or value
|
||||
expect(screen.getByText('entire-secret')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('lock-icon')).toBeInTheDocument();
|
||||
|
||||
// Verify secret routing
|
||||
expect(mockLink).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'kubernetes.secrets.secret',
|
||||
params: {
|
||||
name: 'entire-secret',
|
||||
namespace: 'default',
|
||||
},
|
||||
'data-cy': 'configmap-link-entire-secret',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should render init containers with asterisk indicator', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'main-container',
|
||||
image: 'main-image',
|
||||
env: [
|
||||
{
|
||||
name: 'MAIN_VAR',
|
||||
value: 'main-value',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
initContainers: [
|
||||
{
|
||||
name: 'init-container',
|
||||
image: 'init-image',
|
||||
env: [
|
||||
{
|
||||
name: 'INIT_VAR',
|
||||
value: 'init-value',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationEnvVarsTable namespace="default" app={app} />);
|
||||
|
||||
// Check main container
|
||||
expect(screen.getByText('main-container')).toBeInTheDocument();
|
||||
expect(screen.getByText('MAIN_VAR')).toBeInTheDocument();
|
||||
expect(screen.getByText('main-value')).toBeInTheDocument();
|
||||
|
||||
// Check init container
|
||||
expect(screen.getByText('init-container')).toBeInTheDocument();
|
||||
expect(screen.getByText('INIT_VAR')).toBeInTheDocument();
|
||||
expect(screen.getByText('init-value')).toBeInTheDocument();
|
||||
expect(screen.getByText('init container')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('asterisk-icon')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle mixed environment variable types correctly', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
env: [
|
||||
{
|
||||
name: 'REGULAR_VAR',
|
||||
value: 'regular-value',
|
||||
},
|
||||
{
|
||||
name: 'CONFIG_VAR',
|
||||
valueFrom: {
|
||||
configMapKeyRef: {
|
||||
name: 'test-configmap',
|
||||
key: 'config-key',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'SECRET_VAR',
|
||||
valueFrom: {
|
||||
secretKeyRef: {
|
||||
name: 'test-secret',
|
||||
key: 'secret-key',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
envFrom: [
|
||||
{
|
||||
configMapRef: {
|
||||
name: 'entire-configmap',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationEnvVarsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getAllByText('test-container')).toHaveLength(4); // Should appear 4 times - once for each env var
|
||||
expect(screen.getByText('REGULAR_VAR')).toBeInTheDocument();
|
||||
expect(screen.getByText('regular-value')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('CONFIG_VAR')).toHaveLength(2); // Appears in name and value columns
|
||||
// Note: config-key is not displayed in UI - the component shows the env var name
|
||||
expect(screen.getAllByText('SECRET_VAR')).toHaveLength(2); // Appears in name and value columns
|
||||
// Note: secret-key is not displayed in UI - the component shows the env var name
|
||||
expect(screen.getByText('test-configmap')).toBeInTheDocument();
|
||||
expect(screen.getByText('test-secret')).toBeInTheDocument();
|
||||
expect(screen.getByText('entire-configmap')).toBeInTheDocument();
|
||||
|
||||
// Should have made multiple Link calls
|
||||
expect(mockLink).toHaveBeenCalledTimes(3);
|
||||
|
||||
// Verify different routing calls
|
||||
expect(mockLink).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'kubernetes.configmaps.configmap',
|
||||
})
|
||||
);
|
||||
|
||||
expect(mockLink).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'kubernetes.secrets.secret',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle Deployment kind applications', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-deployment', namespace: 'default' },
|
||||
spec: {
|
||||
selector: {
|
||||
matchLabels: {
|
||||
app: 'test-app',
|
||||
},
|
||||
},
|
||||
template: {
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
env: [
|
||||
{
|
||||
name: 'ENV_VAR',
|
||||
value: 'test-value',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
kind: 'Deployment',
|
||||
apiVersion: 'apps/v1',
|
||||
};
|
||||
|
||||
render(<ApplicationEnvVarsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('test-container')).toBeInTheDocument();
|
||||
expect(screen.getByText('ENV_VAR')).toBeInTheDocument();
|
||||
expect(screen.getByText('test-value')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle missing resource names gracefully', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
env: [
|
||||
{
|
||||
name: 'CONFIG_VAR',
|
||||
valueFrom: {
|
||||
configMapKeyRef: {
|
||||
// name is undefined
|
||||
key: 'config-key',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationEnvVarsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('test-container')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('CONFIG_VAR')).toHaveLength(2); // Appears in name and value columns
|
||||
// Note: config-key is not displayed in UI - the component shows the env var name
|
||||
|
||||
// Should show dash for missing resource name
|
||||
const dashElements = screen.getAllByText('-');
|
||||
expect(dashElements.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should handle containers without environment variables', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
// No env or envFrom
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationEnvVarsTable namespace="default" app={app} />);
|
||||
|
||||
expect(
|
||||
screen.getByText('Environment variables, ConfigMaps or Secrets')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('text-tip')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'This application is not using any environment variable, ConfigMap or Secret.'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle environment variables without keys', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
env: [
|
||||
{
|
||||
name: '', // Empty name to test this edge case
|
||||
value: 'test-value',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationEnvVarsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('test-container')).toBeInTheDocument();
|
||||
expect(screen.getByText('test-value')).toBeInTheDocument();
|
||||
|
||||
// Should show dash for missing env var name
|
||||
const dashElements = screen.getAllByText('-');
|
||||
expect(dashElements.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should render multiple containers with different environment variable types', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'container-1',
|
||||
image: 'image-1',
|
||||
env: [
|
||||
{
|
||||
name: 'CONFIG_VAR',
|
||||
valueFrom: {
|
||||
configMapKeyRef: {
|
||||
name: 'shared-config',
|
||||
key: 'config-key',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'container-2',
|
||||
image: 'image-2',
|
||||
env: [
|
||||
{
|
||||
name: 'SECRET_VAR',
|
||||
valueFrom: {
|
||||
secretKeyRef: {
|
||||
name: 'shared-config', // Same name but different type
|
||||
key: 'secret-key',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationEnvVarsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('container-1')).toBeInTheDocument();
|
||||
expect(screen.getByText('container-2')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('CONFIG_VAR')).toHaveLength(2); // Appears in name and value columns
|
||||
expect(screen.getAllByText('SECRET_VAR')).toHaveLength(2); // Appears in name and value columns
|
||||
expect(screen.getAllByText('shared-config')).toHaveLength(2);
|
||||
|
||||
// Should have made two Link calls - one for configmap, one for secret
|
||||
expect(mockLink).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Verify configmap routing
|
||||
expect(mockLink).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'kubernetes.configmaps.configmap',
|
||||
params: {
|
||||
name: 'shared-config',
|
||||
namespace: 'default',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Verify secret routing
|
||||
expect(mockLink).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'kubernetes.secrets.secret',
|
||||
params: {
|
||||
name: 'shared-config',
|
||||
namespace: 'default',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
|
@ -84,8 +84,8 @@ export function ApplicationEnvVarsTable({ namespace, app }: Props) {
|
|||
))}
|
||||
</td>
|
||||
<td data-cy="k8sAppDetail-configName">
|
||||
{!envVar.resourseName && <span>-</span>}
|
||||
{envVar.resourseName && (
|
||||
{!envVar.resourceName && <span>-</span>}
|
||||
{envVar.resourceName && (
|
||||
<span>
|
||||
<Link
|
||||
to={
|
||||
|
@ -94,17 +94,17 @@ export function ApplicationEnvVarsTable({ namespace, app }: Props) {
|
|||
: 'kubernetes.secrets.secret'
|
||||
}
|
||||
params={{
|
||||
name: envVar.resourseName,
|
||||
name: envVar.resourceName,
|
||||
namespace,
|
||||
}}
|
||||
className="flex items-center"
|
||||
data-cy={`configmap-link-${envVar.resourseName}`}
|
||||
data-cy={`configmap-link-${envVar.resourceName}`}
|
||||
>
|
||||
<Icon
|
||||
icon={envVar.type === 'configMap' ? FileCode : Lock}
|
||||
className="!mr-1"
|
||||
/>
|
||||
{envVar.resourseName}
|
||||
{envVar.resourceName}
|
||||
</Link>
|
||||
</span>
|
||||
)}
|
||||
|
@ -126,7 +126,7 @@ interface ContainerEnvVar {
|
|||
containerName: string;
|
||||
isInitContainer: boolean;
|
||||
type: EnvVarType;
|
||||
resourseName: string;
|
||||
resourceName: string;
|
||||
}
|
||||
|
||||
function getApplicationEnvironmentVariables(
|
||||
|
@ -159,7 +159,7 @@ function getApplicationEnvironmentVariables(
|
|||
containerName: container.name,
|
||||
isInitContainer: false,
|
||||
type: envtype,
|
||||
resourseName:
|
||||
resourceName:
|
||||
envVar?.valueFrom?.configMapKeyRef?.name ||
|
||||
envVar?.valueFrom?.secretKeyRef?.name ||
|
||||
'',
|
||||
|
@ -170,7 +170,7 @@ function getApplicationEnvironmentVariables(
|
|||
const containerEnvFroms: ContainerEnvVar[] =
|
||||
container?.envFrom?.map((envFrom) => ({
|
||||
name: '',
|
||||
resourseName:
|
||||
resourceName:
|
||||
envFrom?.configMapRef?.name || envFrom?.secretRef?.name || '',
|
||||
containerName: container.name,
|
||||
isInitContainer: false,
|
||||
|
@ -196,7 +196,7 @@ function getApplicationEnvironmentVariables(
|
|||
containerName: container.name,
|
||||
isInitContainer: true,
|
||||
type: envtype,
|
||||
resourseName:
|
||||
resourceName:
|
||||
envVar?.valueFrom?.configMapKeyRef?.name ||
|
||||
envVar?.valueFrom?.secretKeyRef?.name ||
|
||||
'',
|
||||
|
@ -207,7 +207,7 @@ function getApplicationEnvironmentVariables(
|
|||
const containerEnvFroms: ContainerEnvVar[] =
|
||||
container?.envFrom?.map((envFrom) => ({
|
||||
name: '',
|
||||
resourseName:
|
||||
resourceName:
|
||||
envFrom?.configMapRef?.name || envFrom?.secretRef?.name || '',
|
||||
containerName: container.name,
|
||||
isInitContainer: true,
|
||||
|
|
|
@ -0,0 +1,583 @@
|
|||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { vi, beforeEach, afterEach } from 'vitest';
|
||||
|
||||
import { Application } from '../../types';
|
||||
|
||||
import { ApplicationVolumeConfigsTable } from './ApplicationVolumeConfigsTable';
|
||||
|
||||
// Mock icon components
|
||||
vi.mock('lucide-react', () => ({
|
||||
Asterisk: () => <span data-cy="asterisk-icon" />,
|
||||
Plus: () => <span data-cy="plus-icon" />,
|
||||
}));
|
||||
|
||||
// Mock UI components
|
||||
vi.mock('@@/Icon', () => ({
|
||||
Icon: ({
|
||||
icon: IconComponent,
|
||||
...props
|
||||
}: {
|
||||
icon: React.ComponentType;
|
||||
[key: string]: unknown;
|
||||
}) => <IconComponent {...props} />,
|
||||
}));
|
||||
|
||||
// Mock the Link component to capture routing props
|
||||
const mockLink = vi.fn();
|
||||
vi.mock('@@/Link', () => ({
|
||||
Link: ({
|
||||
children,
|
||||
to,
|
||||
params,
|
||||
'data-cy': dataCy,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
to: string;
|
||||
params: Record<string, string>;
|
||||
'data-cy'?: string;
|
||||
className?: string;
|
||||
}) => {
|
||||
mockLink({ children, to, params, 'data-cy': dataCy, className });
|
||||
return (
|
||||
<span
|
||||
data-cy={dataCy}
|
||||
data-testid={dataCy}
|
||||
role="link"
|
||||
data-to={to}
|
||||
data-params={JSON.stringify(params)}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
describe('ApplicationVolumeConfigsTable', () => {
|
||||
beforeEach(() => {
|
||||
mockLink.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render nothing when there are no volume configurations', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
const { container } = render(
|
||||
<ApplicationVolumeConfigsTable namespace="default" app={app} />
|
||||
);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('should render nothing when app is undefined', () => {
|
||||
const { container } = render(
|
||||
<ApplicationVolumeConfigsTable namespace="default" app={undefined} />
|
||||
);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('should render volume configurations from configmaps with items', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
volumeMounts: [
|
||||
{
|
||||
name: 'config-volume',
|
||||
mountPath: '/etc/config',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
volumes: [
|
||||
{
|
||||
name: 'config-volume',
|
||||
configMap: {
|
||||
name: 'test-configmap',
|
||||
items: [
|
||||
{
|
||||
key: 'config-key',
|
||||
path: 'config.yaml',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationVolumeConfigsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('test-container')).toBeInTheDocument();
|
||||
expect(screen.getByText('/etc/config/config.yaml')).toBeInTheDocument();
|
||||
expect(screen.getByText('config-key')).toBeInTheDocument();
|
||||
expect(screen.getAllByTestId('plus-icon')).toHaveLength(2); // One for value, one for link
|
||||
expect(screen.getByText('test-configmap')).toBeInTheDocument();
|
||||
|
||||
// Verify the Link component was called with correct routing parameters
|
||||
expect(mockLink).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'kubernetes.configmaps.configmap',
|
||||
params: {
|
||||
name: 'test-configmap',
|
||||
namespace: 'default',
|
||||
},
|
||||
'data-cy': 'config-link-test-configmap',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should render volume configurations from secrets with items', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
volumeMounts: [
|
||||
{
|
||||
name: 'secret-volume',
|
||||
mountPath: '/etc/secrets',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
volumes: [
|
||||
{
|
||||
name: 'secret-volume',
|
||||
secret: {
|
||||
secretName: 'test-secret',
|
||||
items: [
|
||||
{
|
||||
key: 'secret-key',
|
||||
path: 'secret.txt',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationVolumeConfigsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('test-container')).toBeInTheDocument();
|
||||
expect(screen.getByText('/etc/secrets/secret.txt')).toBeInTheDocument();
|
||||
expect(screen.getByText('secret-key')).toBeInTheDocument();
|
||||
expect(screen.getAllByTestId('plus-icon')).toHaveLength(2); // One for value, one for link
|
||||
expect(screen.getByText('test-secret')).toBeInTheDocument();
|
||||
|
||||
// Verify the Link component was called with correct routing parameters for secret
|
||||
expect(mockLink).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'kubernetes.secrets.secret',
|
||||
params: {
|
||||
name: 'test-secret',
|
||||
namespace: 'default',
|
||||
},
|
||||
'data-cy': 'secret-link-test-secret',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should render init containers with asterisk indicator', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'main-container',
|
||||
image: 'main-image',
|
||||
volumeMounts: [
|
||||
{
|
||||
name: 'config-volume',
|
||||
mountPath: '/etc/config',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
initContainers: [
|
||||
{
|
||||
name: 'init-container',
|
||||
image: 'init-image',
|
||||
volumeMounts: [
|
||||
{
|
||||
name: 'config-volume',
|
||||
mountPath: '/etc/init-config',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
volumes: [
|
||||
{
|
||||
name: 'config-volume',
|
||||
configMap: {
|
||||
name: 'shared-config',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationVolumeConfigsTable namespace="default" app={app} />);
|
||||
|
||||
// Check main container
|
||||
expect(screen.getByText('main-container')).toBeInTheDocument();
|
||||
expect(screen.getByText('/etc/config')).toBeInTheDocument();
|
||||
|
||||
// Check init container
|
||||
expect(screen.getByText('init-container')).toBeInTheDocument();
|
||||
expect(screen.getByText('/etc/init-config')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('asterisk-icon')).toBeInTheDocument();
|
||||
expect(screen.getByText('init container')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render secret volume configurations correctly based on type', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
volumeMounts: [
|
||||
{
|
||||
name: 'secret-volume',
|
||||
mountPath: '/etc/secrets',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
volumes: [
|
||||
{
|
||||
name: 'secret-volume',
|
||||
secret: {
|
||||
secretName: 'test-secret',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationVolumeConfigsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('test-container')).toBeInTheDocument();
|
||||
expect(screen.getByText('test-secret')).toBeInTheDocument();
|
||||
|
||||
// Should route to secret page because type is 'secret'
|
||||
expect(mockLink).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'kubernetes.secrets.secret',
|
||||
params: {
|
||||
name: 'test-secret',
|
||||
namespace: 'default',
|
||||
},
|
||||
'data-cy': 'secret-link-test-secret',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should render configmap volume configurations correctly based on type', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
volumeMounts: [
|
||||
{
|
||||
name: 'config-volume',
|
||||
mountPath: '/etc/config',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
volumes: [
|
||||
{
|
||||
name: 'config-volume',
|
||||
configMap: {
|
||||
name: 'test-configmap',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationVolumeConfigsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('test-container')).toBeInTheDocument();
|
||||
expect(screen.getByText('test-configmap')).toBeInTheDocument();
|
||||
|
||||
// Should route to configmap page because type is 'configMap'
|
||||
expect(mockLink).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'kubernetes.configmaps.configmap',
|
||||
params: {
|
||||
name: 'test-configmap',
|
||||
namespace: 'default',
|
||||
},
|
||||
'data-cy': 'config-link-test-configmap',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle volumes without items (entire volume mount)', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
volumeMounts: [
|
||||
{
|
||||
name: 'config-volume',
|
||||
mountPath: '/etc/config',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
volumes: [
|
||||
{
|
||||
name: 'config-volume',
|
||||
configMap: {
|
||||
name: 'test-configmap',
|
||||
// No items - entire configmap is mounted
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationVolumeConfigsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('test-container')).toBeInTheDocument();
|
||||
expect(screen.getByText('/etc/config')).toBeInTheDocument();
|
||||
expect(screen.getByText('-')).toBeInTheDocument(); // No specific key
|
||||
expect(screen.getByText('test-configmap')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle multiple volumes with different types correctly', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
volumeMounts: [
|
||||
{
|
||||
name: 'secret-volume',
|
||||
mountPath: '/etc/secrets',
|
||||
},
|
||||
{
|
||||
name: 'config-volume',
|
||||
mountPath: '/etc/config',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
volumes: [
|
||||
{
|
||||
name: 'secret-volume',
|
||||
secret: {
|
||||
secretName: 'test-secret',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'config-volume',
|
||||
configMap: {
|
||||
name: 'test-configmap',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationVolumeConfigsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getAllByText('test-container')).toHaveLength(2); // Should appear twice - once for each volume
|
||||
expect(screen.getByText('test-secret')).toBeInTheDocument();
|
||||
expect(screen.getByText('test-configmap')).toBeInTheDocument();
|
||||
|
||||
// Should have made two Link calls - one for secret, one for configmap
|
||||
expect(mockLink).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Verify secret link
|
||||
expect(mockLink).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'kubernetes.secrets.secret',
|
||||
params: {
|
||||
name: 'test-secret',
|
||||
namespace: 'default',
|
||||
},
|
||||
'data-cy': 'secret-link-test-secret',
|
||||
})
|
||||
);
|
||||
|
||||
// Verify configmap link
|
||||
expect(mockLink).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: 'kubernetes.configmaps.configmap',
|
||||
params: {
|
||||
name: 'test-configmap',
|
||||
namespace: 'default',
|
||||
},
|
||||
'data-cy': 'config-link-test-configmap',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle containers without volume mounts', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
// No volumeMounts
|
||||
},
|
||||
],
|
||||
volumes: [
|
||||
{
|
||||
name: 'config-volume',
|
||||
configMap: {
|
||||
name: 'test-configmap',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
const { container } = render(
|
||||
<ApplicationVolumeConfigsTable namespace="default" app={app} />
|
||||
);
|
||||
|
||||
// Should render nothing because there are no matching volume mounts
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle Deployment kind applications', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-deployment', namespace: 'default' },
|
||||
spec: {
|
||||
selector: {
|
||||
matchLabels: {
|
||||
app: 'test-app',
|
||||
},
|
||||
},
|
||||
template: {
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
volumeMounts: [
|
||||
{
|
||||
name: 'config-volume',
|
||||
mountPath: '/etc/config',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
volumes: [
|
||||
{
|
||||
name: 'config-volume',
|
||||
configMap: {
|
||||
name: 'test-configmap',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
kind: 'Deployment',
|
||||
apiVersion: 'apps/v1',
|
||||
};
|
||||
|
||||
render(<ApplicationVolumeConfigsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('test-container')).toBeInTheDocument();
|
||||
expect(screen.getByText('/etc/config')).toBeInTheDocument();
|
||||
expect(screen.getByText('test-configmap')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle missing volume config names', () => {
|
||||
const app: Application = {
|
||||
metadata: { name: 'test-pod', namespace: 'default' },
|
||||
spec: {
|
||||
containers: [
|
||||
{
|
||||
name: 'test-container',
|
||||
image: 'test-image',
|
||||
volumeMounts: [
|
||||
{
|
||||
name: 'config-volume',
|
||||
mountPath: '/etc/config',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
volumes: [
|
||||
{
|
||||
name: 'config-volume',
|
||||
configMap: {
|
||||
// name is undefined
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
kind: 'Pod',
|
||||
apiVersion: 'v1',
|
||||
};
|
||||
|
||||
render(<ApplicationVolumeConfigsTable namespace="default" app={app} />);
|
||||
|
||||
expect(screen.getByText('test-container')).toBeInTheDocument();
|
||||
expect(screen.getByText('/etc/config')).toBeInTheDocument();
|
||||
|
||||
// Should show dash for missing volume config name
|
||||
const dashElements = screen.getAllByText('-');
|
||||
expect(dashElements.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
|
@ -1,15 +1,23 @@
|
|||
import { KeyToPath, Pod, Secret } from 'kubernetes-types/core/v1';
|
||||
import { KeyToPath, Pod, VolumeMount } from 'kubernetes-types/core/v1';
|
||||
import { Asterisk, Plus } from 'lucide-react';
|
||||
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
import { useK8sSecrets } from '@/react/kubernetes/configs/queries/useK8sSecrets';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
import { Link } from '@@/Link';
|
||||
|
||||
import { Application } from '../../types';
|
||||
import { applicationIsKind } from '../../utils';
|
||||
|
||||
type VolumeConfigType = 'configMap' | 'secret';
|
||||
|
||||
type AppVolumeConfig = {
|
||||
volumeConfigName: string | undefined;
|
||||
containerVolumeMount: VolumeMount | undefined;
|
||||
containerName: string;
|
||||
isInitContainer: boolean;
|
||||
item: KeyToPath;
|
||||
type: VolumeConfigType;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
namespace: string;
|
||||
app?: Application;
|
||||
|
@ -18,8 +26,6 @@ type Props = {
|
|||
export function ApplicationVolumeConfigsTable({ namespace, app }: Props) {
|
||||
const containerVolumeConfigs = getApplicationVolumeConfigs(app);
|
||||
|
||||
const { data: secrets } = useK8sSecrets(useEnvironmentId(), namespace);
|
||||
|
||||
if (containerVolumeConfigs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
@ -41,6 +47,7 @@ export function ApplicationVolumeConfigsTable({ namespace, app }: Props) {
|
|||
containerName,
|
||||
item,
|
||||
volumeConfigName,
|
||||
type,
|
||||
},
|
||||
index
|
||||
) => (
|
||||
|
@ -76,7 +83,7 @@ export function ApplicationVolumeConfigsTable({ namespace, app }: Props) {
|
|||
{!item.key && '-'}
|
||||
</td>
|
||||
<td>
|
||||
{isVolumeConfigNameFromSecret(secrets, volumeConfigName) ? (
|
||||
{type === 'secret' ? (
|
||||
<Link
|
||||
className="flex items-center"
|
||||
to="kubernetes.secrets.secret"
|
||||
|
@ -107,15 +114,8 @@ export function ApplicationVolumeConfigsTable({ namespace, app }: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
function isVolumeConfigNameFromSecret(
|
||||
secrets?: Secret[],
|
||||
volumeConfigName?: string
|
||||
) {
|
||||
return secrets?.some((secret) => secret.metadata?.name === volumeConfigName);
|
||||
}
|
||||
|
||||
// getApplicationVolumeConfigs returns a list of volume configs / secrets for each container and each item within the matching volume
|
||||
function getApplicationVolumeConfigs(app?: Application) {
|
||||
function getApplicationVolumeConfigs(app?: Application): AppVolumeConfig[] {
|
||||
if (!app) {
|
||||
return [];
|
||||
}
|
||||
|
@ -142,6 +142,10 @@ function getApplicationVolumeConfigs(app?: Application) {
|
|||
const containerVolumeMount = container.volumeMounts?.find(
|
||||
(volumeMount) => volumeMount.name === volume.name
|
||||
);
|
||||
const type: VolumeConfigType = volume.configMap
|
||||
? 'configMap'
|
||||
: 'secret';
|
||||
|
||||
if (volConfigMapItems.length === 0) {
|
||||
return [
|
||||
{
|
||||
|
@ -150,6 +154,7 @@ function getApplicationVolumeConfigs(app?: Application) {
|
|||
containerName: container.name,
|
||||
isInitContainer: appInitContainers.includes(container),
|
||||
item: {} as KeyToPath,
|
||||
type,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
@ -160,6 +165,7 @@ function getApplicationVolumeConfigs(app?: Application) {
|
|||
containerName: container.name,
|
||||
isInitContainer: appInitContainers.includes(container),
|
||||
item,
|
||||
type,
|
||||
}));
|
||||
})
|
||||
// only return the app volumes where the container volumeMounts include the volume name (from map step above)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue