diff --git a/src/test/tree/images/NormalizedImageNameInfo.test.ts b/src/test/tree/images/NormalizedImageNameInfo.test.ts new file mode 100644 index 0000000000..1a192d3e34 --- /dev/null +++ b/src/test/tree/images/NormalizedImageNameInfo.test.ts @@ -0,0 +1,178 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert = require('assert'); +import { ImageNameInfo } from '../../../runtimes/docker'; +import { NormalizedImageNameInfo } from '../../../tree/images/NormalizedImageNameInfo'; + +// The expected values for each of these test cases are in the methods below +// The order of the test cases must exactly match the order of the expected values +const testCases: ImageNameInfo[] = [ + { + originalName: 'alpine', + registry: undefined, + image: 'alpine', + tag: 'latest', + }, + { + originalName: 'myapp', + registry: undefined, + image: 'myapp', + tag: 'latest', + }, + { + originalName: 'mynamespace/myapp', + registry: undefined, + image: 'mynamespace/myapp', + tag: '1.0', + }, + { + originalName: 'mcr.microsoft.com/dotnet/runtime', + registry: 'mcr.microsoft.com', + image: 'dotnet/runtime', + tag: 'latest', + }, + { + originalName: 'myregistry.contoso.com/someapp:5.0', + registry: 'myregistry.contoso.com', + image: 'someapp', + tag: '5.0', + }, + { + originalName: 'myregistry.contoso.com/with/longer/namespace/someapp:5.0', + registry: 'myregistry.contoso.com', + image: 'with/longer/namespace/someapp', + tag: '5.0', + }, + { + originalName: '', + registry: undefined, + image: undefined, + tag: undefined, + }, + { + originalName: 'alpine', + registry: undefined, + image: 'alpine', + tag: undefined, + } +]; + +suite('(unit) NormalizedImageNameInfo Tests', () => { + + suite(('normalizedImageName'), () => { + const expected = [ + 'alpine', + 'myapp', + 'mynamespace/myapp', + 'dotnet/runtime', + 'someapp', + 'with/longer/namespace/someapp', + '', + 'alpine', + ]; + + testPropertyAgainstExpectedValues('normalizedImageName', expected); + }); + + suite(('normalizedTag'), () => { + const expected = [ + 'latest', + 'latest', + '1.0', + 'latest', + '5.0', + '5.0', + '', + '', + ]; + + testPropertyAgainstExpectedValues('normalizedTag', expected); + }); + + suite(('normalizedImageNameAndTag'), () => { + const expected = [ + 'alpine:latest', + 'myapp:latest', + 'mynamespace/myapp:1.0', + 'dotnet/runtime:latest', + 'someapp:5.0', + 'with/longer/namespace/someapp:5.0', + '', + 'alpine', + ]; + + testPropertyAgainstExpectedValues('normalizedImageNameAndTag', expected); + }); + + suite(('fullTag'), () => { + const expected = [ + 'alpine:latest', + 'myapp:latest', + 'mynamespace/myapp:1.0', + 'mcr.microsoft.com/dotnet/runtime:latest', + 'myregistry.contoso.com/someapp:5.0', + 'myregistry.contoso.com/with/longer/namespace/someapp:5.0', + '', + 'alpine', + ]; + + testPropertyAgainstExpectedValues('fullTag', expected); + }); + + suite(('normalizedNamespace'), () => { + const expected = [ + 'library', + 'library', + 'mynamespace', + 'dotnet', + undefined, + 'with/longer/namespace', + 'library', + 'library' + ]; + + testPropertyAgainstExpectedValues('normalizedNamespace', expected); + }); + + suite(('normalizedRegistry'), () => { + const expected = [ + 'docker.io', + 'docker.io', + 'docker.io', + 'mcr.microsoft.com', + 'myregistry.contoso.com', + 'myregistry.contoso.com', + 'docker.io', + 'docker.io', + ]; + + testPropertyAgainstExpectedValues('normalizedRegistry', expected); + }); + + suite(('normalizedRegistryAndImageName'), () => { + const expected = [ + 'alpine', + 'myapp', + 'docker.io/mynamespace/myapp', + 'mcr.microsoft.com/dotnet/runtime', + 'myregistry.contoso.com/someapp', + 'myregistry.contoso.com/with/longer/namespace/someapp', + '', + 'alpine', + ]; + + testPropertyAgainstExpectedValues('normalizedRegistryAndImageName', expected); + }); +}); + +function testPropertyAgainstExpectedValues(property: string, expected: string[]): void { + testCases.forEach((testCase, index) => { + test(testCase.originalName, () => { + const normalizedImageNameInfo = new NormalizedImageNameInfo(testCase); + assert.strictEqual(normalizedImageNameInfo[property], expected[index]); + }); + }); +} diff --git a/src/tree/images/NormalizedImageNameInfo.ts b/src/tree/images/NormalizedImageNameInfo.ts index 6c3bb876a9..450ebae1db 100644 --- a/src/tree/images/NormalizedImageNameInfo.ts +++ b/src/tree/images/NormalizedImageNameInfo.ts @@ -49,15 +49,18 @@ export class NormalizedImageNameInfo { } /** - * The part of the image name before the first '/' (if image name is truthy and contains '/'), otherwise 'library' (i.e. the 'library' in 'docker.io/library') + * The part of the image name before the last '/' (if image name is truthy and contains '/'), otherwise 'library' if the + * normalized registry is 'docker.io', otherwise undefined */ - public get normalizedNamespace(): string { + public get normalizedNamespace(): string | undefined { let i: number; - if ((i = this.normalizedImageName.indexOf('/')) >= 0) { + if ((i = this.normalizedImageName.lastIndexOf('/')) >= 0) { return this.normalizedImageName.substring(0, i); + } else if (this.normalizedRegistry === 'docker.io') { + return 'library'; + } else { + return undefined; } - - return 'library'; } /** @@ -71,6 +74,14 @@ export class NormalizedImageNameInfo { * Normalized registry + normalized image name */ public get normalizedRegistryAndImageName(): string { - return `${this.normalizedRegistry}/${this.normalizedImageName}`; + // If the registry is explicitly or implicitly `docker.io`, and the namespace is explicitly or implicitly `library`, + // then we don't show either of those parts as the image name--that most closely matches the Docker CLI's default + // display behavior. As a slight but intentional deviation from that behavior, if an image is namespaced, we'll + // include the implicit `docker.io`. + if (this.normalizedRegistry === 'docker.io' && this.normalizedNamespace === 'library') { + return this.normalizedImageName; + } else { + return `${this.normalizedRegistry}/${this.normalizedImageName}`; + } } }