Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Reports warnings on failed kube deploy, fixes error out #3050

Merged
merged 3 commits into from Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
142 changes: 142 additions & 0 deletions packages/renderer/src/lib/kube/KubePlayYAML.spec.ts
@@ -0,0 +1,142 @@
/**********************************************************************
* Copyright (C) 2023 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/

import '@testing-library/jest-dom';
import { test, expect, vi } from 'vitest';
import { providerInfos } from '../../stores/providers';
import { render, screen } from '@testing-library/svelte';
import KubePlayYAML from './KubePlayYAML.svelte';
import type { ProviderStatus } from '@podman-desktop/api';
import type { ProviderContainerConnectionInfo } from '../../../../main/src/plugin/api/provider-info';
import userEvent from '@testing-library/user-event';
import type { PlayKubeInfo } from '../../../../main/src/plugin/dockerode/libpod-dockerode';

const mockedErroredPlayKubeInfo: PlayKubeInfo = {
Pods: [
{
ContainerErrors: ['error 1', 'error 2'],
Containers: ['container 1', 'container 2'],
Id: 'pod-id',
InitContainers: ['init-container 1', 'init-container 2'],
Logs: ['log 1', 'log 2'],
},
],
RmReport: [
{
Err: 'rm error',
Id: 'rm-id',
},
],
Secrets: [
{
CreateReport: {
ID: 'secret-id',
},
},
],
StopReport: [
{
Err: 'stop error',
Id: 'stop-id',
},
],
Volumes: [
{
Name: 'volume 1',
},
{
Name: 'volume 2',
},
],
};

// fake the window.events object
beforeAll(() => {
(window.events as unknown) = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
receive: (_channel: string, func: any) => {
func();
},
};
(window as any).getConfigurationValue = vi.fn();
(window as any).matchMedia = vi.fn().mockReturnValue({
addListener: vi.fn(),
});
(window as any).openFileDialog = vi.fn().mockResolvedValue({ canceled: false, filePaths: ['Containerfile'] });
(window as any).telemetryPage = vi.fn();
(window as any).kubernetesGetCurrentContextName = vi.fn();
(window as any).kubernetesGetCurrentNamespace = vi.fn();
(window as any).kubernetesListNamespaces = vi.fn();
});

function setup() {
const pStatus: ProviderStatus = 'started';
const pInfo: ProviderContainerConnectionInfo = {
name: 'test',
status: 'started',
endpoint: {
socketPath: '',
},
type: 'podman',
};
const providerInfo = {
id: 'test',
internalId: 'id',
name: '',
containerConnections: [pInfo],
kubernetesConnections: undefined,
status: pStatus,
containerProviderConnectionCreation: false,
containerProviderConnectionInitialization: false,
kubernetesProviderConnectionCreation: false,
kubernetesProviderConnectionInitialization: false,
links: undefined,
detectionChecks: undefined,
warnings: undefined,
images: undefined,
installationSupport: undefined,
};
providerInfos.set([providerInfo]);
}

test('error: When pressing the Play button, expect us to show the errors to the user', async () => {
(window as any).playKube = vi.fn().mockResolvedValue(mockedErroredPlayKubeInfo);

// Render the component
setup();
render(KubePlayYAML, {});

// Simulate selecting a file
const fileInput = screen.getByRole('textbox', { name: 'Kubernetes YAML file' });
expect(fileInput).toBeInTheDocument();
await userEvent.click(fileInput);

// Simulate selecting a runtime
const runtimeOption = screen.getByText('Using a Podman container engine');
expect(runtimeOption).toBeInTheDocument();

// Simulate clicking the "Play" button
const playButton = screen.getByRole('button', { name: 'Play' });
expect(playButton).toBeInTheDocument();
await userEvent.click(playButton);

// Since we error out with the mocked kubePlay function (see very top of tests)
// Expect the following error to be in in the document.
const error = screen.getByText('The following pods were created but failed to start: error 1, error 2');
expect(error).toBeInTheDocument();
});
64 changes: 56 additions & 8 deletions packages/renderer/src/lib/kube/KubePlayYAML.svelte
Expand Up @@ -10,13 +10,15 @@ import NoContainerEngineEmptyScreen from '../image/NoContainerEngineEmptyScreen.
import NavPage from '../ui/NavPage.svelte';
import KubePlayIcon from '../kube/KubePlayIcon.svelte';
import ErrorMessage from '../ui/ErrorMessage.svelte';
import WarningMessage from '../ui/WarningMessage.svelte';
import type { V1NamespaceList } from '@kubernetes/client-node/dist/api';
import { faCircleCheck } from '@fortawesome/free-solid-svg-icons';
import Fa from 'svelte-fa/src/fa.svelte';

let runStarted = false;
let runFinished = false;
let runError = '';
let runWarning = '';
let kubernetesYamlFilePath = undefined;
let hasInvalidFields = true;

Expand All @@ -25,6 +27,7 @@ let currentNamespace: string;
let allNamespaces: V1NamespaceList;

let playKubeResultRaw;
let playKubeResultJSON;

let userChoice: 'podman' | 'kubernetes' = 'podman';

Expand Down Expand Up @@ -57,8 +60,28 @@ async function playKubeFile(): Promise<void> {
if (userChoice === 'podman') {
try {
const result = await window.playKube(kubernetesYamlFilePath, selectedProvider);

// remove the null values from the result
playKubeResultRaw = JSON.stringify(removeEmptyOrNull(result), null, 2);
playKubeResultJSON = JSON.parse(playKubeResultRaw);

// If there are container errors, that means that it was *able* to create the container
// but if failed to start. We will add this to the "warning" section as we were able to create the
// We add this with comma deliminated errors
if (playKubeResultJSON.Pods.length > 0) {
// Filter out the pods that have container errors, but check to see that container errors exists first
const containerErrors = playKubeResultJSON.Pods.filter(
pod => pod.ContainerErrors && pod.ContainerErrors.length > 0,
);

// For each Pod that has container errors, we will add the container errors to the warning message
if (containerErrors.length > 0) {
runWarning = `The following pods were created but failed to start: ${containerErrors
.map(pod => pod.ContainerErrors.join(', '))
.join(', ')}`;
}
}

runFinished = true;
} catch (error) {
runError = error;
Expand Down Expand Up @@ -107,6 +130,10 @@ onDestroy(() => {
}
});

function goBackToHistory(): void {
window.history.go(-1);
}

async function getKubernetesfileLocation() {
const result = await window.openFileDialog('Select a .yaml file to play', {
name: 'YAML files',
Expand Down Expand Up @@ -274,17 +301,38 @@ async function getKubernetesfileLocation() {
complete...
</div>
{/if}
<div class="text-sm"><ErrorMessage error="{runError}" /></div>

{#if runWarning}
<WarningMessage class="text-sm" error="{runWarning}" />
{/if}

{#if runError}
<ErrorMessage class="text-sm" error="{runError}" />
{/if}

{#if playKubeResultJSON}
<!-- Output area similar to DeployPodToKube.svelte -->
<div class="bg-charcoal-800 p-5 my-4">
<div class="flex flex-row items-center">
<div>
{#if playKubeResultJSON?.Pods.length > 1}
Created pods:
{:else}
Created pod:
{/if}
</div>
</div>

<div class="h-[100px] pt-2">
<MonacoEditor content="{playKubeResultRaw}" language="json" />
</div>
</div>
{/if}

{#if runFinished}
<!-- On click, go BACK to the previous page (if clicked on Pods page, go back to pods, same for Containers)-->
<button on:click="{() => history.back()}" class="w-full pf-c-button pf-m-primary">Done</button>
<button on:click="{() => goBackToHistory()}" class="pt-4 w-full pf-c-button pf-m-primary">Done</button>
{/if}
</div>
{#if playKubeResultRaw}
<div class=" h-full w-full px-6 pb-4 space-y-6 lg:px-8 sm:pb-6 xl:pb-8">
<MonacoEditor content="{playKubeResultRaw}" language="json" />
</div>
{/if}
</div>
</NavPage>
{/if}