Skip to content
This repository has been archived by the owner on Jan 8, 2024. It is now read-only.

Commit

Permalink
ui: improve docker reference parsing
Browse files Browse the repository at this point in the history
Closes #2484
  • Loading branch information
jgwhite committed Oct 19, 2021
1 parent 22a200f commit 6e9539a
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 38 deletions.
3 changes: 3 additions & 0 deletions .changelog/2518.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
ui: improve docker reference parsing
```
21 changes: 14 additions & 7 deletions ui/app/components/image-ref.hbs
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
<span data-test-image-ref class="image-ref" title="Image">
<span class="image-ref__label">
{{@imageRef.label}}
</span>
<span class="image-ref__tag">
{{#if (eq @imageRef.label "sha256")}}
{{truncate-commit @imageRef.tag}}
<span class="image-ref__path" data-test-image-ref-path>
{{#if @imageRef.domain}}
{{@imageRef.domain}}/{{@imageRef.path}}
{{else}}
{{@imageRef.tag}}
{{@imageRef.path}}
{{/if}}
</span>
{{#if @imageRef.tag}}
<span class="image-ref__tag" data-test-image-ref-tag>
{{@imageRef.tag}}
</span>
{{/if}}
{{#if @imageRef.digest}}
<span class="image-ref__digest" data-test-image-ref-digest>
{{@imageRef.digest}}
</span>
{{/if}}
</span>
5 changes: 4 additions & 1 deletion ui/app/styles/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ $button-shadow: 0 3px 2px rgba(var(--shadow), 0.2);
--text: #{dehex(color.$black)};
--text-muted: #{dehex(color.$ui-cool-gray-600)};
--text-subtle: #{dehex(color.$ui-cool-gray-400)};
--tag-background: #{dehex(color.$ui-gray-100)};
--link: #{dehex(color.$blue-500)};
--border: #{dehex(color.$ui-gray-200)};
--outline: #{dehex(color.$ui-gray-300)};
--card: #{dehex(color.$white)};
--panel: #{dehex(color.$ui-cool-gray-050)};
--badge: #{dehex(color.$blue-050)};
--badge-text: #{dehex(color.$blue-500)};
--badge-neutral: #{dehex(color.$ui-cool-gray-100)};
--badge-neutral-text: #{dehex(color.$ui-cool-gray-700)};
--shadow: #{dehex(color.$ui-cool-gray-900)};
--focus-ring: #{dehex(color.$blue-500)};
--success: #{dehex(color.$green-050)};
Expand Down Expand Up @@ -57,6 +58,8 @@ $button-shadow: 0 3px 2px rgba(var(--shadow), 0.2);
--focus-ring: #{dehex(color.$blue-400)};
--badge: #{dehex(color.$ui-gray-800)};
--badge-text: #{dehex(color.$ui-gray-300)};
--badge-neutral: #{dehex(color.$ui-gray-800)};
--badge-neutral-text: #{dehex(color.$ui-gray-300)};
--success: #{dehex(color.$green-900)};
--success-text: #{dehex(color.$green-400)};
--success-border: #{dehex(color.$green-800)};
Expand Down
8 changes: 5 additions & 3 deletions ui/app/styles/components/image-ref.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
display: flex;
align-items: center;

&__tag {
background: rgb(var(--tag-background));
color: rgb(var(--text-muted));
&__tag,
&__digest {
background: rgb(var(--badge-neutral));
color: rgb(var(--badge-neutral-text));

border-radius: 2px;
padding: 0 4px;
margin-left: 7px;
Expand Down
40 changes: 16 additions & 24 deletions ui/app/utils/image-refs.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,13 @@
export class ImageRef {
ref: string;

constructor(ref: string) {
this.ref = ref;
}

get label(): string {
return this.split[0];
}

get tag(): string {
return this.split[1];
}

private get split(): string[] {
return this.ref.split(':');
}
}
import parse, { Ref } from 'parse-docker-image-name';

/**
* Returns a flat map of values for `image` properties within the given object.
*
* @param {object|array} obj search space
* @param {ImageRef[]} [result=[]] starting result array (used internally, usually no need to pass this)
* @returns {ImageRef[]} an array of found ImageRefs
* @param {Ref[]} [result=[]] starting result array (used internally, usually no need to pass this)
* @returns {Ref[]} an array of found ImageRefs
*/
export function findImageRefs(obj: unknown, result: ImageRef[] = []): ImageRef[] {
export function findImageRefs(obj: unknown, result: Ref[] = []): Ref[] {
if (typeof obj !== 'object') {
return result;
}
Expand All @@ -36,9 +18,19 @@ export function findImageRefs(obj: unknown, result: ImageRef[] = []): ImageRef[]

for (let [key, value] of Object.entries(obj)) {
if (key.toLowerCase() === 'image' && typeof value === 'string') {
if (!result.some((image) => image.ref === value)) {
result.push(new ImageRef(value));
if (result[value]) {
// We’ve already seen this ref, continue.
continue;
}

let ref = parse(value);

result.push(ref);

// The result array also acts as a map of ref strings to the resultant Ref
// objects. This little trick is purely internal. Think of it as a “seen”
// list.
result[value] = ref;
} else {
findImageRefs(value, result);
}
Expand Down
2 changes: 1 addition & 1 deletion ui/mirage/factories/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export default Factory.extend({
spec: {
containers: [
{
image: 'marketing-public/wp-matrix:1',
image: 'localhost:5000/image-name:latest',
},
],
},
Expand Down
1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
"faker": "^5.1.0",
"loader.js": "^4.7.0",
"npm-run-all": "^4.1.5",
"parse-docker-image-name": "^3.0.0",
"parse-url": "^5.0.2",
"pretender": "^3.4.3",
"prettier": "^2.2.1",
Expand Down
58 changes: 56 additions & 2 deletions ui/tests/integration/components/container-image-tag-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@ module('Integration | Component | container-image-tag', function (hooks) {
});

await render(hbs`<ContainerImageTag @statusReport={{this.statusReport}}/>`);
assert.equal(this.element?.textContent?.trim().replace(/\s+/, ' '), 'docker tag');

assert.dom('[data-test-image-ref-path]').hasText('docker');
assert.dom('[data-test-image-ref-tag]').hasText('tag');
});

test('it renders n/a when a status report does not exist', async function (assert) {
this.set('statusReport2', {});

await render(hbs`<ContainerImageTag @statusReport={{this.statusReport2}}/>`);
assert.equal(this.element?.textContent?.trim().replace(/\s+/, ' '), 'n/a');

assert.dom(this.element).hasText('n/a');
});

test('it renders multiple tags', async function (assert) {
Expand All @@ -39,4 +42,55 @@ module('Integration | Component | container-image-tag', function (hooks) {
await render(hbs`<ContainerImageTag @statusReport={{this.multiStatus}}/>`);
assert.dom('[data-test-image-ref]').exists({ count: 2 });
});

test('it handles refs like "localhost:5000/image-name:latest"', async function (assert) {
this.set('statusReport', {
resourcesList: [
{
type: 'container',
stateJson: '{"Config": {"Image": "localhost:5000/image-name:latest"}}',
},
],
});

await render(hbs`<ContainerImageTag @statusReport={{this.statusReport}}/>`);

assert.dom('[data-test-image-ref-path]').hasText('localhost:5000/image-name');
assert.dom('[data-test-image-ref-tag]').hasText('latest');
});

test('it handles refs like "localhost:5000/image-name"', async function (assert) {
this.set('statusReport', {
resourcesList: [
{
type: 'container',
stateJson: '{"Config": {"Image": "localhost:5000/image-name"}}',
},
],
});

await render(hbs`<ContainerImageTag @statusReport={{this.statusReport}}/>`);

assert.dom('[data-test-image-ref-path]').hasText('localhost:5000/image-name');
assert.dom('[data-test-image-ref-tag]').doesNotExist();
});

test('it handles refs with digests', async function (assert) {
this.set('statusReport', {
resourcesList: [
{
type: 'container',
stateJson:
'{"Config": {"Image": "localhost:5000/image-name@sha256:aaaaf56b44807c64d294e6c8059b479f35350b454492398225034174808d1726"}}',
},
],
});

await render(hbs`<ContainerImageTag @statusReport={{this.statusReport}}/>`);

assert.dom('[data-test-image-ref-path]').hasText('localhost:5000/image-name');
assert
.dom('[data-test-image-ref-digest]')
.hasText('sha256:aaaaf56b44807c64d294e6c8059b479f35350b454492398225034174808d1726');
});
});
10 changes: 10 additions & 0 deletions ui/types/parse-docker-image-name.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
declare module 'parse-docker-image-name' {
interface Ref {
domain?: string;
path: string;
digest?: string;
tag?: string;
}

export default function parse(input: string): Ref;
}
5 changes: 5 additions & 0 deletions ui/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11317,6 +11317,11 @@ parse-bmfont-xml@^1.1.4:
xml-parse-from-string "^1.0.0"
xml2js "^0.4.5"

parse-docker-image-name@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/parse-docker-image-name/-/parse-docker-image-name-3.0.0.tgz#1ea03afaf46499f5dfba2953927cfb5a6ed097d5"
integrity sha512-sxJ3KBv/8dXZ+E2cbJFFI9rLqgxtRgRuMv534b1g7hdWRxoB8tudlyyWONafEHO8itQSM0XWfMDodykLWAh5kQ==

parse-headers@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.3.tgz#5e8e7512383d140ba02f0c7aa9f49b4399c92515"
Expand Down

0 comments on commit 6e9539a

Please sign in to comment.