Skip to content

Commit

Permalink
feat: add inspect tab to compose (#3316)
Browse files Browse the repository at this point in the history
### What does this PR do?

* Adds the inspect tab to Compose which is an array of "container
  inspect" from docker / podman.
* Inspect is JSON array of containers, see
  docker/compose#4155 for inspiration (they
  don't actually have a `compose inspect` command..)
* Adds tests

### Screenshot/screencast of this PR

<!-- Please include a screenshot or a screencast explaining what is doing this PR -->

### What issues does this PR fix or reference?

<!-- Please include any related issue from Podman Desktop repository (or from another issue tracker).
-->

Closes #3192

### How to test this PR?

1. Deploy a compose example
2. Click on the compose group
3. Select the inspect tab

<!-- Please explain steps to reproduce -->

Signed-off-by: Charlie Drage <charlie@charliedrage.com>
  • Loading branch information
cdrage committed Jul 26, 2023
1 parent ae23457 commit 4c52dd5
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 4 deletions.
169 changes: 166 additions & 3 deletions packages/renderer/src/lib/compose/ComposeDetails.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { test, expect, vi } from 'vitest';
import { fireEvent, render, screen } from '@testing-library/svelte';
import ComposeDetails from './ComposeDetails.svelte';
import { mockBreadcrumb } from '../../stores/breadcrumb';
import type { ContainerInspectInfo } from '@podman-desktop/api';
import { containersInfos } from '../../stores/containers';
import { get } from 'svelte/store';

const listContainersMock = vi.fn();

vi.mock('xterm', () => {
return {
Expand All @@ -11,6 +16,10 @@ vi.mock('xterm', () => {
});

beforeAll(() => {
const onDidUpdateProviderStatusMock = vi.fn();
(window as any).onDidUpdateProviderStatus = onDidUpdateProviderStatusMock;
onDidUpdateProviderStatusMock.mockImplementation(() => Promise.resolve());

(window.events as unknown) = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
receive: (_channel: string, func: any) => {
Expand All @@ -21,15 +30,125 @@ beforeAll(() => {
(window as any).matchMedia = vi.fn().mockReturnValue({
addListener: vi.fn(),
});
(window as any).telemetryPage = vi.fn().mockReturnValue({
sendPageView: vi.fn(),
});
(window as any).ResizeObserver = vi.fn().mockReturnValue({ observe: vi.fn(), unobserve: vi.fn() });
(window as any).initializeProvider = vi.fn().mockResolvedValue([]);
(window as any).getContainerInspect = vi.fn().mockResolvedValue(containerInspectInfo);
(window as any).listNetworks = vi.fn().mockResolvedValue([]);
(window as any).listContainers = listContainersMock;
(window as any).logsContainer = vi.fn();
(window as any).listViewsContributions = vi.fn();
(window as any).telemetryPage = vi.fn().mockResolvedValue(undefined);
(window as any).generatePodmanKube = vi.fn();
mockBreadcrumb();
});

const containerInspectInfo: ContainerInspectInfo = {
engineId: '',
engineName: '',
Id: '',
Created: '',
Path: '',
Args: [],
State: {
Status: '',
Running: false,
Paused: false,
Restarting: false,
OOMKilled: false,
Dead: false,
Pid: 0,
ExitCode: 0,
Error: '',
StartedAt: '',
FinishedAt: '',
Health: {
Status: '',
FailingStreak: 0,
Log: [],
},
},
Image: '',
ResolvConfPath: '',
HostnamePath: '',
HostsPath: '',
LogPath: '',
Name: '',
RestartCount: 0,
Driver: '',
Platform: '',
MountLabel: '',
ProcessLabel: '',
AppArmorProfile: '',
HostConfig: {
PortBindings: {
9090: [
{
HostPort: 8383,
HostIp: '',
},
],
},
},
GraphDriver: {
Name: '',
Data: {
DeviceId: '',
DeviceName: '',
DeviceSize: '',
},
},
Mounts: [],
Config: {
Hostname: '',
Domainname: '',
User: '',
AttachStdin: false,
AttachStdout: false,
AttachStderr: false,
ExposedPorts: {},
Tty: false,
OpenStdin: false,
StdinOnce: false,
Env: [],
Cmd: [],
Image: '',
Volumes: {},
WorkingDir: '',
Entrypoint: '',
OnBuild: undefined,
Labels: {},
},
NetworkSettings: {
Bridge: '',
SandboxID: '',
HairpinMode: false,
LinkLocalIPv6Address: '',
LinkLocalIPv6PrefixLen: 0,
Ports: {},
SandboxKey: '',
SecondaryIPAddresses: undefined,
SecondaryIPv6Addresses: undefined,
EndpointID: '',
Gateway: '',
GlobalIPv6Address: '',
GlobalIPv6PrefixLen: 0,
IPAddress: '',
IPPrefixLen: 0,
IPv6Gateway: '',
MacAddress: '',
Networks: {},
Node: {
ID: '',
IP: '',
Addr: '',
Name: '',
Cpus: 0,
Memory: 0,
Labels: undefined,
},
},
};

test('Simple test that compose logs are clickable and loadable', async () => {
render(ComposeDetails, { composeName: 'foobar', engineId: 'engine' });
// Click on the logs href
Expand All @@ -46,6 +165,50 @@ test('Simple test that compose name is displayed', async () => {
expect(screen.getByText('foobar')).toBeInTheDocument();
});

test('Compose details inspect is clickable and loadable', async () => {
const mockedContainers = [
{
Id: 'sha256:1234567890123',
Image: 'sha256:123',
Names: ['foo'],
Status: 'Running',
engineId: 'podman',
engineName: 'podman',
Labels: {
'com.docker.compose.project': 'foobar',
},
},
{
Id: 'sha256:1234567890123',
Image: 'sha256:123',
Names: ['foo2'],
Status: 'Running',
engineId: 'podman',
engineName: 'podman',
Labels: {
'com.docker.compose.project': 'foobar',
},
},
];

listContainersMock.mockResolvedValue(mockedContainers);

window.dispatchEvent(new CustomEvent('extensions-already-started'));
window.dispatchEvent(new CustomEvent('provider-lifecycle-change'));
window.dispatchEvent(new CustomEvent('tray:update-provider'));

// wait store are populated
while (get(containersInfos).length === 0) {
await new Promise(resolve => setTimeout(resolve, 500));
}

render(ComposeDetails, { composeName: 'foobar', engineId: 'podman' });

// Click on the inspect href that it renders correctly / displays the correct data
const inspectHref = screen.getByRole('link', { name: 'Inspect' });
await fireEvent.click(inspectHref);
});

test('Test that compose kube tab is clickable and loadable', async () => {
render(ComposeDetails, { composeName: 'foobar', engineId: 'engine' });
const kubeHref = screen.getByRole('link', { name: 'Kube' });
Expand Down
5 changes: 5 additions & 0 deletions packages/renderer/src/lib/compose/ComposeDetails.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import ComposeDetailsKube from './ComposeDetailsKube.svelte';
import type { ContainerInfoUI } from '../container/ContainerInfoUI';
import DetailsPage from '../ui/DetailsPage.svelte';
import Tab from '../ui/Tab.svelte';
import ComposeDetailsInspect from './ComposeDetailsInspect.svelte';
export let composeName: string;
export let engineId: string;
Expand Down Expand Up @@ -84,12 +85,16 @@ onDestroy(() => {
</svelte:fragment>
<svelte:fragment slot="tabs">
<Tab title="Logs" url="logs" />
<Tab title="Inspect" url="inspect" />
<Tab title="Kube" url="kube" />
</svelte:fragment>
<svelte:fragment slot="content">
<Route path="/logs" breadcrumb="Logs" navigationHint="tab">
<ComposeDetailsLogs compose="{compose}" />
</Route>
<Route path="/inspect" breadcrumb="Inspect" navigationHint="tab">
<ComposeDetailsInspect compose="{compose}" />
</Route>
<Route path="/kube" breadcrumb="Kube" navigationHint="tab">
<ComposeDetailsKube compose="{compose}" />
</Route>
Expand Down
31 changes: 31 additions & 0 deletions packages/renderer/src/lib/compose/ComposeDetailsInspect.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script lang="ts">
import type { ComposeInfoUI } from './ComposeInfoUI';
import { onMount } from 'svelte';
import MonacoEditor from '../editor/MonacoEditor.svelte';
export let compose: ComposeInfoUI;
let inspectDetails: string;
onMount(async () => {
// Go through each container and grab the inspect result, add it to inspectDetails / stringify
const mappedResults = await Promise.all(
compose.containers.map(async container => {
const inspectResult = await window.getContainerInspect(container.engineId, container.id);
// remove engine* properties from the inspect result as it's more internal
delete inspectResult.engineId;
delete inspectResult.engineName;
return inspectResult;
}),
);
// stringify the results
inspectDetails = JSON.stringify(mappedResults, undefined, 2);
});
</script>

{#if inspectDetails}
<MonacoEditor content="{inspectDetails}" language="json" />
{/if}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ beforeAll(() => {
addListener: vi.fn(),
});
(window as any).openFileDialog = vi.fn().mockResolvedValue({ canceled: false, filePaths: ['Containerfile'] });
(window as any).telemetryPage = vi.fn();
(window as any).telemetryPage = vi.fn().mockResolvedValue(undefined);
});

// the build image page expects to have a valid provider connection, so let's mock one
Expand Down

0 comments on commit 4c52dd5

Please sign in to comment.