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

fix(container-detail): display the entire command instead of the first piece only #6604

Closed
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/main/src/plugin/api/container-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export interface ContainerInfo {
Names: string[];
Image: string;
ImageID: string;
Command?: string;
Command?: readonly string[];
Copy link
Contributor

@axel7083 axel7083 Apr 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, you're totally right. However, I changed it to readonly string[] because listContainers dynamically selects among various providers, leading to the use of workarounds such as podmanContainer.Command?.length > 0 ? podmanContainer.Command[0]: undefined. In my opinion, ContainerInfo should use an array to represent a command even though dockerode returns a string because it enables us to not lose data(in this case other parts of the command).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I looked at the podman repository and they are indeed sending an array compared to dockerode definition.

https://github.com/containers/podman/blob/17f36a369035b4d5c27f416af9cb961ede807eed/pkg/domain/entities/types/container_ps.go#L14-L15

I would be in favour to get something closer to what podman provide, the changes you are providing only touch the internal api, so it would not have much side effect, however the ContainerInfo is also provided to the extension-api.

export interface ContainerInfo {

However for the extension-api we have something different, with an interface called SimpleContainerInfo which is some extending of dockerode.

export interface SimpleContainerInfo extends Dockerode.ContainerInfo {

And I am wondering if we change/fix it at one place we would not have to propagate the changes also elsewhere... Waiting for @benoitf opinion on the matter

Created: number;
Ports: ContainerPortInfo[];
Labels: { [label: string]: string };
Expand Down
6 changes: 3 additions & 3 deletions packages/main/src/plugin/container-registry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ describe('listContainers', () => {
expect(container.StartedAt).toBe('2023-08-10T13:37:44.000Z');
expect(container.pod).toBeUndefined();
expect(container.Id).toBe('31a4b282691420be2611817f203765402d8da7e13cd530f80a6ddd1bb4aa63b4');
expect(container.Command).toBe('httpd-foreground');
expect(container.Command).toStrictEqual(['httpd-foreground']);
expect(container.Names).toStrictEqual(['/admiring_wing']);
expect(container.Image).toBe('docker.io/library/httpd:latest');
expect(container.ImageID).toBe('sha256:911d72fc5020723f0c003a134a8d2f062b4aea884474a11d1db7dcd28ce61d6a');
Expand Down Expand Up @@ -936,7 +936,7 @@ describe('listContainers', () => {
expect(container.pod).toBeUndefined();

expect(container.Id).toBe('31a4b282691420be2611817f203765402d8da7e13cd530f80a6ddd1bb4aa63b4');
expect(container.Command).toBe('httpd-foreground');
expect(container.Command).toStrictEqual(['httpd-foreground']);
expect(container.Names).toStrictEqual(['/admiring_wing']);
expect(container.Image).toBe('docker.io/library/httpd:latest');
expect(container.ImageID).toBe('sha256:911d72fc5020723f0c003a134a8d2f062b4aea884474a11d1db7dcd28ce61d6a');
Expand Down Expand Up @@ -1031,7 +1031,7 @@ describe('listContainers', () => {
expect(container.StartedAt).toBe('2023-08-10T13:37:44.000Z');
expect(container.pod).toBeUndefined();
expect(container.Id).toBe('31a4b282691420be2611817f203765402d8da7e13cd530f80a6ddd1bb4aa63b4');
expect(container.Command).toBe(undefined);
expect(container.Command).toBe(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure to understand why null would be used here instead of undefined

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, libpodApi returns null if the command is empty, whereas dockerode returns undefined

expect(container.Names).toStrictEqual(['/admiring_wing']);
expect(container.Image).toBe('docker.io/library/httpd:latest');
expect(container.ImageID).toBe('sha256:911d72fc5020723f0c003a134a8d2f062b4aea884474a11d1db7dcd28ce61d6a');
Expand Down
6 changes: 3 additions & 3 deletions packages/main/src/plugin/container-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ export class ContainerProviderRegistry {
Names: string[];
Image: string;
ImageID: string;
Command?: string;
Command?: string[] | string;
Created: number;
Ports: ContainerPortInfo[];
Labels: { [label: string]: string };
Expand All @@ -435,7 +435,6 @@ export class ContainerProviderRegistry {
}

// if we have a libpod API, grab containers using Podman API
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let containers: CompatContainerInfo[] = [];
if (provider.libpodApi) {
const podmanContainers = await provider.libpodApi.listPodmanContainers({ all: true });
Expand Down Expand Up @@ -476,7 +475,7 @@ export class ContainerProviderRegistry {
Created: moment(podmanContainer.Created).unix(),
State: podmanContainer.State,
StartedAt,
Command: podmanContainer.Command?.length > 0 ? podmanContainer.Command[0] : undefined,
Command: podmanContainer.Command,
GLEF1X marked this conversation as resolved.
Show resolved Hide resolved
Labels,
Ports,
};
Expand Down Expand Up @@ -533,6 +532,7 @@ export class ContainerProviderRegistry {
engineType: provider.connection.type,
StartedAt: container.StartedAt || '',
Status: container.Status,
Command: typeof container.Command === 'string' ? [container.Command] : container.Command,
};
return containerInfo;
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const containerInfoUIMock: ContainerInfoUI = {
ports: [],
portsAsString: 'foobar',
displayPort: 'foobar',
command: 'foobar',
command: ['foobar'],
hasPublicPort: false,
groupInfo: {
name: 'foobar',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const myContainer: ContainerInfo = {
Names: ['name0'],
Image: '',
ImageID: '',
Command: '',
Command: [''],
Created: 0,
Ports: [],
State: '',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**********************************************************************
* 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/vitest';

import { render, screen } from '@testing-library/svelte';
import { describe, expect, test } from 'vitest';

import ContainerDetailsSummary from './ContainerDetailsSummary.svelte';
import type { ContainerInfoUI } from './ContainerInfoUI';

test('Expect container command to be displayed as an array', async () => {
const container: ContainerInfoUI = {
shortId: 'myContainer',
command: ['sh', '-c', 'something', 'else'],
state: 'RUNNING',
hasPublicPort: false,
} as unknown as ContainerInfoUI;

render(ContainerDetailsSummary, { container });
expect(screen.getByTestId('container-details-command-td')).toHaveTextContent('["sh", "-c", "something", "else"]');
});

describe('Expect command to be hidden if command is an empty array or undefined', async () => {
// eslint-disable-next-line no-null/no-null
test.each([[], undefined, null])('given %p should not be visible', async command => {
const container: ContainerInfoUI = {
shortId: 'myContainer',
command: command,
state: 'RUNNING',
hasPublicPort: false,
} as unknown as ContainerInfoUI;

render(ContainerDetailsSummary, { container });
expect(screen.getByTestId('container-details-command-tr').classList.contains('hidden')).to.be.true;
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<script lang="ts">
import { ContainerUtils } from './container-utils';
import type { ContainerInfoUI } from './ContainerInfoUI';

export let container: ContainerInfoUI;
const containerUtils = new ContainerUtils();
</script>

<div class="flex px-5 py-4 flex-col h-full overflow-auto">
Expand All @@ -12,9 +14,10 @@ export let container: ContainerInfoUI;
<td class="pr-2">Id</td>
<td>{container.shortId}</td>
</tr>
<tr class:hidden="{!container.command}">
<tr data-testid="container-details-command-tr" class:hidden="{!container.command?.length}">
<td class="pr-2">Command</td>
<td>{container.command}</td>
<td data-testid="container-details-command-td"
>{container.command && containerUtils.formatContainerCommand(container.command)}</td>
</tr>
<tr>
<td class="pr-2">State</td>
Expand Down
2 changes: 1 addition & 1 deletion packages/renderer/src/lib/container/ContainerInfoUI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export interface ContainerInfoUI {
ports: Port[];
portsAsString: string;
displayPort: string;
command?: string;
command?: readonly string[];
hasPublicPort: boolean;
openingUrl?: string;
groupInfo: ContainerGroupPartInfoUI;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const myContainer: ContainerInfoUI = {
ports: [],
portsAsString: 'foobar',
displayPort: 'foobar',
command: 'foobar',
command: ['foobar'],
hasPublicPort: false,
groupInfo: {
name: 'foobar',
Expand Down
5 changes: 5 additions & 0 deletions packages/renderer/src/lib/container/container-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ export class ContainerUtils {
return humanizeDuration(uptimeInMs, { round: true, largest: 1 });
}

formatContainerCommand(command: readonly string[]): string {
const commandArrayString = command.map(commandPiece => `"` + commandPiece + `"`).join(', ');
return `[${commandArrayString}]`;
}

refreshUptime(containerInfoUI: ContainerInfoUI): string {
if (containerInfoUI.state !== 'RUNNING' || !containerInfoUI.startedAt) {
return '';
Expand Down