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 22, 2021
1 parent e3de6f4 commit 3c5d2a4
Show file tree
Hide file tree
Showing 18 changed files with 251 additions and 72 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
```
4 changes: 2 additions & 2 deletions ui/app/components/container-image-tag/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import ApiService from 'waypoint/services/api';
import { StatusReport } from 'waypoint-pb';
import { ImageRef, findImageRefs } from 'waypoint/utils/image-refs';
import { findImageRefs } from 'waypoint/utils/image-refs';

interface Args {
statusReport: StatusReport.AsObject;
Expand All @@ -17,7 +17,7 @@ export default class extends Component<Args> {
: [];
}

get imageRefs(): ImageRef[] {
get imageRefs(): ReturnType<typeof findImageRefs> {
return findImageRefs(this.states);
}
}
3 changes: 1 addition & 2 deletions ui/app/components/git-commit.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@
<Pds::Icon @type="git-commit"/><span class="git-commit__sha">{{truncate-commit @commit}}</span>
</CopyButton>
</span>
{{/if}}

{{/if}}
28 changes: 19 additions & 9 deletions ui/app/components/image-ref.hbs
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
<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}}
{{else}}
{{@imageRef.tag}}
{{/if}}
<span class="image-ref__uri" data-test-image-ref-uri>
{{this.uri}}
</span>

{{#if this.hasTag}}
<span class="image-ref__tag badge badge--info" data-test-image-ref-tag>
{{this.presentableTag}}
</span>
{{/if}}

{{#if this.tagIsDigest}}
<CopyButton
@clipboardText={{@imageRef.tag}}
@success={{perform this.displayCopySuccess}}
class="image-ref__copy-button focus-ring"
title={{t "image-ref.copy-button-title"}}
>
<Pds::Icon @type={{if this.displayCopySuccess.isRunning "copy-success" "copy-action"}} />
</CopyButton>
{{/if}}
</span>
51 changes: 51 additions & 0 deletions ui/app/components/image-ref.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Ember from 'ember';
import Component from '@glimmer/component';
import { Ref } from 'docker-parse-image';
import { action } from '@ember/object';
import { TaskGenerator, task, timeout } from 'ember-concurrency';
import { taskFor } from 'ember-concurrency-ts';

type Args = {
imageRef?: Ref;
};

export default class extends Component<Args> {
get uri(): string {
if (!this.args.imageRef) {
return '';
}

let { registry, namespace, repository } = this.args.imageRef;

return [registry, namespace, repository].filter(Boolean).join('/');
}

get hasTag(): boolean {
return !!this.args.imageRef?.tag;
}

get tagIsDigest(): boolean {
return this.args.imageRef?.tag?.includes(':') ?? false;
}

get presentableTag(): string | undefined {
let tag = this.args.imageRef?.tag;

if (!tag) {
return;
}

if (this.tagIsDigest) {
let [alg, digest] = tag.split(':');
return `${alg}:${digest.substr(0, 7)}`;
}

return tag;
}

@task({ restartable: true })
*displayCopySuccess(): TaskGenerator<void> {
let duration = Ember.testing ? 0 : 2000;
yield timeout(duration);
}
}
4 changes: 2 additions & 2 deletions ui/app/components/resource-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import RouterService from '@ember/routing/router-service';
import { StatusReport } from 'waypoint-pb';
import { ImageRef, findImageRefs } from 'waypoint/utils/image-refs';
import { findImageRefs } from 'waypoint/utils/image-refs';

interface Args {
resource?: StatusReport.Resource.AsObject;
Expand Down Expand Up @@ -37,7 +37,7 @@ export default class extends Component<Args> {
return this.state?.pod?.metadata?.labels;
}

get imageRefs(): ImageRef[] {
get imageRefs(): ReturnType<typeof findImageRefs> {
return findImageRefs(this.state);
}

Expand Down
24 changes: 15 additions & 9 deletions ui/app/components/status-report-meta-table/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,31 @@
<table>
<tbody>
{{#if @model.statusReport}}
<tr>
<th scope="row">
{{t "page.deployment.overview.image"}}
</th>
<td>
<ContainerImageTag @statusReport={{@model.statusReport}}/>
</td>
</tr>
<tr>
<th scope="row">{{t "page.deployment.overview.health-check"}}</th>
<td>
<div class="status-indicator">
<StatusReportIndicator @statusReport={{@model.statusReport}} /> &nbsp;
<Pds::Button @variant="ghost" @iconStart="refresh-default" disabled={{this.isRefreshRunning}} {{on "click" this.refreshHealthCheck}}>
<Pds::Button
@variant="ghost"
@iconStart="refresh-default"
class="refresh-health-check-button"
disabled={{this.isRefreshRunning}}
{{on "click" this.refreshHealthCheck}}
>
{{t "page.deployment.overview.re-run-health-check"}}
</Pds::Button>
</div>
</td>
</tr>
<tr>
<th scope="row">
{{t "page.deployment.overview.image"}}
</th>
<td>
<ContainerImageTag @statusReport={{@model.statusReport}}/>
</td>
</tr>
{{else}}
{{t "page.deployment.overview.unavailable"}}
{{/if}}
Expand Down
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
26 changes: 22 additions & 4 deletions ui/app/styles/components/image-ref.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,28 @@
align-items: center;

&__tag {
background: rgb(var(--tag-background));
color: rgb(var(--text-muted));
border-radius: 2px;
padding: 0 4px;
margin-left: 7px;
}

&__copy-button {
display: flex;
align-items: center;
justify-content: center;
background: transparent;
color: inherit;
font-size: inherit;
line-height: inherit;

padding: scale.$sm--4;
border: 0;
margin: 0;
margin-left: 0.25rem;

appearance: none;
cursor: pointer;
}

& + & {
margin-top: scale.$sm--4;
}
}
41 changes: 26 additions & 15 deletions ui/app/styles/components/navigation/artifact-overview.scss
Original file line number Diff line number Diff line change
@@ -1,35 +1,46 @@
.artifact-overview {
padding-bottom: 0.5rem;

h2 {
border-bottom: 1px solid rgb(var(--border));
padding-bottom: 0.5rem;
}

table {
padding: 0.5rem 0;
padding: 0;
font-size: 0.875rem;
line-height: 17px;

tr {
th {
font-weight: 500;
color: rgb(var(--text-muted));
width: 158px;
text-align: left;
padding-bottom: 0.25rem;
}
th,
td {
padding-left: 0;
padding-top: scale.$sm--2;
padding-bottom: scale.$sm--2;
vertical-align: top;
}

.status-indicator {
display: flex;
align-items: flex-end;
padding-bottom: 0.25rem;
}
th {
font-weight: normal;
color: rgb(var(--text-muted));
padding-right: scale.$lg--10;
text-align: left;
}

button {
.status-indicator {
display: flex;
align-items: flex-end;
padding-bottom: 0.25rem;
}

.refresh-health-check-button {
padding: 0 0.25rem;
height: inherit;
display: flex;
align-items: baseline;
}

.image-ref:first-child {
margin-top: -1px;
}
}
}
4 changes: 4 additions & 0 deletions ui/app/styles/components/resource-detail.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
padding-bottom: scale.$sm--2;
vertical-align: top;
}

.image-ref:first-child {
margin-top: -1px;
}
}

&__flex {
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 'docker-parse-image';

/**
* 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
11 changes: 10 additions & 1 deletion ui/mirage/factories/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,16 @@ export default Factory.extend({
spec: {
containers: [
{
image: 'marketing-public/wp-matrix:1',
image: 'marketing-public/wp-matrix@sha256:c47cbb1d0526ad29183fb14919ff6c757ec31173',
},
{
image: 'localhost:5000/wp-matrix:a-very-long-but-still-human-readable-tag',
},
{
image: 'marketing-public/wp-matrix:latest',
},
{
image: 'quay.io/marketing-public/wp-matrix',
},
],
},
Expand Down
1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"broccoli-asset-rev": "^3.0.0",
"codemirror": "^5.62.2",
"date-fns": "^2.15.0",
"docker-parse-image": "^3.0.1",
"ember-a11y-testing": "^4.0.7",
"ember-auto-import": "^1.10.1",
"ember-auto-import-typescript": "^0.4.0",
Expand Down
Loading

0 comments on commit 3c5d2a4

Please sign in to comment.