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

feat(oci): oci helm support [r8s-361] (#787)

This commit is contained in:
Ali 2025-07-13 10:37:43 +12:00 committed by GitHub
parent b6a6ce9aaf
commit 2697d6c5d7
80 changed files with 4264 additions and 812 deletions

View file

@ -39,11 +39,6 @@ export function ChartActions({
release={release}
updateRelease={updateRelease}
/>
<UninstallButton
environmentId={environmentId}
releaseName={releaseName}
namespace={namespace}
/>
{showRollbackButton && (
<RollbackButton
latestRevision={latestRevision}
@ -53,6 +48,11 @@ export function ChartActions({
namespace={namespace}
/>
)}
<UninstallButton
environmentId={environmentId}
releaseName={releaseName}
namespace={namespace}
/>
</div>
);
}

View file

@ -25,8 +25,8 @@ vi.mock('@/portainer/services/notifications', () => ({
notifySuccess: vi.fn(),
}));
vi.mock('../../queries/useHelmRegistries', () => ({
useHelmRegistries: vi.fn(() => ({
vi.mock('../../queries/useHelmRepositories', () => ({
useUserHelmRepositories: vi.fn(() => ({
data: ['repo1', 'repo2'],
isInitialLoading: false,
isError: false,
@ -146,7 +146,7 @@ describe('UpgradeButton', () => {
renderButton();
expect(screen.getByText('No versions available')).toBeInTheDocument();
expect(screen.getByText(/No versions available/)).toBeInTheDocument();
});
test('should open upgrade modal when clicked', async () => {

View file

@ -15,7 +15,7 @@ import { HelmRelease, UpdateHelmReleasePayload } from '../../types';
import { useUpdateHelmReleaseMutation } from '../../queries/useUpdateHelmReleaseMutation';
import { useHelmRepoVersions } from '../../queries/useHelmRepoVersions';
import { useHelmRelease } from '../queries/useHelmRelease';
import { useHelmRegistries } from '../../queries/useHelmRegistries';
import { useUserHelmRepositories } from '../../queries/useHelmRepositories';
import { openUpgradeHelmModal } from './UpgradeHelmModal';
@ -36,19 +36,22 @@ export function UpgradeButton({
const [useCache, setUseCache] = useState(true);
const updateHelmReleaseMutation = useUpdateHelmReleaseMutation(environmentId);
const registriesQuery = useHelmRegistries();
const userRepositoriesQuery = useUserHelmRepositories();
const helmRepoVersionsQuery = useHelmRepoVersions(
release?.chart.metadata?.name || '',
60 * 60 * 1000, // 1 hour
registriesQuery.data,
userRepositoriesQuery.data?.map((repo) => ({
repo,
})),
useCache
);
const versions = helmRepoVersionsQuery.data;
// Combined loading state
const isLoading =
registriesQuery.isInitialLoading || helmRepoVersionsQuery.isFetching; // use 'isFetching' for helmRepoVersionsQuery because we want to show when it's refetching
const isError = registriesQuery.isError || helmRepoVersionsQuery.isError;
userRepositoriesQuery.isInitialLoading || helmRepoVersionsQuery.isFetching; // use 'isFetching' for helmRepoVersionsQuery because we want to show when it's refetching
const isError =
userRepositoriesQuery.isError || helmRepoVersionsQuery.isError;
const latestVersionQuery = useHelmRelease(
environmentId,
releaseName,
@ -101,7 +104,7 @@ export function UpgradeButton({
icon={ArrowUp}
size="medium"
>
Upgrade
Edit/Upgrade
</LoadingButton>
{isLoading && (
<InlineLoader

View file

@ -137,6 +137,47 @@ const helmReleaseHistory = [
},
];
// Common MSW handlers for all tests
function createCommonHandlers() {
return [
http.get('/api/users/undefined/helm/repositories', () =>
HttpResponse.json({
GlobalRepository: 'https://charts.helm.sh/stable',
UserRepositories: [{ Id: '1', URL: 'https://charts.helm.sh/stable' }],
})
),
http.get('/api/templates/helm', () =>
HttpResponse.json({
entries: {
'test-chart': [{ version: '1.0.0' }],
},
})
),
http.get('/api/endpoints/3/kubernetes/helm/test-release/history', () =>
HttpResponse.json(helmReleaseHistory)
),
http.get('/api/kubernetes/3/namespaces/default/events', () =>
HttpResponse.json([])
),
http.get('/api/kubernetes/3/namespaces/default', () =>
HttpResponse.json({
Id: 'default',
Name: 'default',
Status: { phase: 'Active' },
Annotations: {},
CreationDate: '2021-01-01T00:00:00Z',
NamespaceOwner: '',
IsSystem: false,
IsDefault: true,
})
),
];
}
function setupMockHandlers(helmReleaseHandler: ReturnType<typeof http.get>) {
server.use(helmReleaseHandler, ...createCommonHandlers());
}
function renderComponent() {
const user = new UserViewModel({ Username: 'user' });
const Wrapped = withTestQueryProvider(
@ -162,30 +203,9 @@ describe(
it('should display helm release details for minimal release when data is loaded', async () => {
vi.spyOn(console, 'error').mockImplementation(() => {});
server.use(
setupMockHandlers(
http.get('/api/endpoints/3/kubernetes/helm/test-release', () =>
HttpResponse.json(minimalHelmRelease)
),
http.get('/api/users/undefined/helm/repositories', () =>
HttpResponse.json({
GlobalRepository: 'https://charts.helm.sh/stable',
UserRepositories: [
{ Id: '1', URL: 'https://charts.helm.sh/stable' },
],
})
),
http.get('/api/templates/helm', () =>
HttpResponse.json({
entries: {
'test-chart': [{ version: '1.0.0' }],
},
})
),
http.get('/api/endpoints/3/kubernetes/helm/test-release/history', () =>
HttpResponse.json(helmReleaseHistory)
),
http.get('/api/kubernetes/3/namespaces/default/events', () =>
HttpResponse.json([])
)
);
@ -224,13 +244,9 @@ describe(
it('should display error message when API request fails', async () => {
// Mock API failure
server.use(
setupMockHandlers(
http.get('/api/endpoints/3/kubernetes/helm/test-release', () =>
HttpResponse.error()
),
// Add mock for events endpoint
http.get('/api/kubernetes/3/namespaces/default/events', () =>
HttpResponse.json([])
)
);
@ -253,15 +269,9 @@ describe(
});
it('should display additional details when available in helm release', async () => {
server.use(
setupMockHandlers(
http.get('/api/endpoints/3/kubernetes/helm/test-release', () =>
HttpResponse.json(completeHelmRelease)
),
http.get('/api/endpoints/3/kubernetes/helm/test-release/history', () =>
HttpResponse.json(helmReleaseHistory)
),
http.get('/api/kubernetes/3/namespaces/default/events', () =>
HttpResponse.json([])
)
);

View file

@ -11,6 +11,7 @@ import { Card } from '@@/Card';
import { Alert } from '@@/Alert';
import { HelmRelease } from '../types';
import { useIsSystemNamespace } from '../../namespaces/queries/useIsSystemNamespace';
import { HelmSummary } from './HelmSummary';
import { ReleaseTabs } from './ReleaseDetails/ReleaseTabs';
@ -37,6 +38,8 @@ export function HelmApplicationView() {
revision: selectedRevision,
});
const isSystemNamespace = useIsSystemNamespace(namespace);
return (
<>
<PageHeader
@ -63,28 +66,30 @@ export function HelmApplicationView() {
/>
</div>
<Authorized authorizations="K8sApplicationsW">
<ChartActions
environmentId={environmentId}
releaseName={String(name)}
namespace={String(namespace)}
latestRevision={latestRevision ?? 1}
earlistRevision={earlistRevision}
selectedRevision={selectedRevision}
release={helmReleaseQuery.data}
updateRelease={(updatedRelease: HelmRelease) => {
queryClient.setQueryData(
[
environmentId,
'helm',
'releases',
namespace,
name,
true,
],
updatedRelease
);
}}
/>
{!isSystemNamespace && (
<ChartActions
environmentId={environmentId}
releaseName={String(name)}
namespace={String(namespace)}
latestRevision={latestRevision ?? 1}
earlistRevision={earlistRevision}
selectedRevision={selectedRevision}
release={helmReleaseQuery.data}
updateRelease={(updatedRelease: HelmRelease) => {
queryClient.setQueryData(
[
environmentId,
'helm',
'releases',
namespace,
name,
true,
],
updatedRelease
);
}}
/>
)}
</Authorized>
</div>
</WidgetTitle>