Skip to content

Commit 520649f

Browse files
authored
πŸ”§ chore(release): v1.5.0-rc.24 (#375)
Cuts v1.5.0-rc.24: #356/#370 Version-column fix, #374 Trivy registry-URL normalization in controller mode, Crowdin translation sync (#373).
1 parent 202f3d8 commit 520649f

5 files changed

Lines changed: 262 additions & 40 deletions

File tree

β€ŽCHANGELOG.mdβ€Ž

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
1111
## [Unreleased]
1212

13+
## [1.5.0-rc.24] β€” 2026-05-17
14+
15+
### Changed
16+
17+
- **Translations refreshed from Crowdin (commit [`202f3d83`](https://github.com/CodesWhat/drydock/commit/202f3d83)).** Human translations were synced from Crowdin for the ~110-string rc.23 i18n extraction sweep, updating the 16 non-English locales across the `appShell`, `containerComponents`, `listViews`, `sharedComponents`, `configView`, `agentsView`, and `notificationOutboxView` namespaces. Strings that were previously falling back to English now render in each locale.
18+
19+
### Fixed
20+
21+
- **[#370](https://github.com/CodesWhat/drydock/issues/370) β€” Containers list "Version" column again shows the human-readable image tag for floating-tag + digest-watch containers, restoring the [#356](https://github.com/CodesWhat/drydock/issues/356) fix that rc.20 inadvertently reverted.** The rc.20 `#342` follow-up (commit `b40d3db8`) added a visible `sha256:… β†’ sha256:…` digest pair to the Containers table "Version" cell and card body for all `updateKind === 'digest'` containers that are not digest-pinned. The intent was to surface the digest transition for hybrid containers where both the tag and the underlying image layer changed simultaneously; however, the change cast too wide a net: it also applied to floating-tag + digest-watch containers (e.g. `prom/prometheus:latest`, `linuxserver/plex` with a transform tag) β€” exactly the rows that [#356](https://github.com/CodesWhat/drydock/issues/356) fixed to show the human-readable tag instead of raw digest strings. The `updateKind === 'digest' && !isDigestPinned` branch of the table version cell and card body in `ui/src/components/containers/ContainersGroupedViews.vue` has been restored to the rc.19 behaviour: the version cell renders `c.currentTag` as a `CopyableTag` (with the full digest delta in the cell tooltip), and the card body shows only the update-state badge (with the digest delta in the badge tooltip). The digest transition remains visible through the adjacent "kind" column update-state indicator and the container detail panels. Digest-*pinned* containers (where `isDigestPinned` is true) are unaffected and continue to show the `sha256:… β†’ sha256:…` pair directly in the cell.
22+
23+
- **[#374](https://github.com/CodesWhat/drydock/issues/374) β€” Security scans no longer hand Trivy a raw registry v2 API URL, which had caused every scan in controller mode to fail.** `resolveContainerImageFullName` (`app/api/container/shared.ts`), used by both the security scan scheduler and the container API, falls back to composing the image reference directly from `container.image.registry.url` whenever the container's registry component is not present in the controller's registry state β€” the normal situation in controller mode (`DD_LOCAL_WATCHER=false`), where registries are configured on the agents rather than on the controller. `registry.url` is stored in the registry v2 API base form (e.g. `https://registry-1.docker.io/v2`), so the fallback produced references such as `https://registry-1.docker.io/v2/dgtlmoon/sockpuppetbrowser:0.0.3`; Trivy then interpreted the scheme as a hostname and every scan failed with `dial tcp: lookup https`. The fallback now mirrors `Registry.getImageFullName`: it strips the URL scheme and the `/v2` path segment and uses an `@` separator for digest references, yielding a plain `registry-1.docker.io/dgtlmoon/sockpuppetbrowser:0.0.3` reference. Containers whose registry component is available are unaffected β€” they already resolved through the correct `getImageFullName` path.
24+
1325
## [1.5.0-rc.23] β€” 2026-05-16
1426

1527
### Added

β€Žapp/api/container/shared.test.tsβ€Ž

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
isSensitiveKey,
77
redactContainerRuntimeEnv,
88
redactContainersRuntimeEnv,
9+
resolveContainerImageFullName,
910
} from './shared.js';
1011

1112
describe('api/container/shared', () => {
@@ -231,4 +232,74 @@ describe('api/container/shared', () => {
231232
expect(descriptor?.enumerable).toBe(false);
232233
});
233234
});
235+
236+
describe('resolveContainerImageFullName', () => {
237+
function makeContainer(
238+
registryName: string,
239+
registryUrl: string,
240+
imageName: string,
241+
tagValue: string,
242+
) {
243+
return {
244+
image: {
245+
registry: { name: registryName, url: registryUrl },
246+
name: imageName,
247+
tag: { value: tagValue },
248+
},
249+
} as never;
250+
}
251+
252+
test('normalizes a Docker Hub v2 API base URL in the fallback branch (issue #374 regression guard)', () => {
253+
const container = makeContainer(
254+
'hub',
255+
'https://registry-1.docker.io/v2',
256+
'dgtlmoon/sockpuppetbrowser',
257+
'0.0.3',
258+
);
259+
const result = resolveContainerImageFullName(container, {});
260+
expect(result).toBe('registry-1.docker.io/dgtlmoon/sockpuppetbrowser:0.0.3');
261+
expect(result.startsWith('https://')).toBe(false);
262+
expect(result).not.toContain('/v2/');
263+
});
264+
265+
test('normalizes a ghcr.io v2 API base URL in the fallback branch', () => {
266+
const container = makeContainer(
267+
'ghcr',
268+
'https://ghcr.io/v2',
269+
'codeswhat/drydock',
270+
'1.5.0-rc.22',
271+
);
272+
const result = resolveContainerImageFullName(container, {});
273+
expect(result).toBe('ghcr.io/codeswhat/drydock:1.5.0-rc.22');
274+
});
275+
276+
test('leaves a plain registry host unchanged in the fallback branch', () => {
277+
const container = makeContainer('hub', 'fallback-registry', 'test/app', '1.2.3');
278+
const result = resolveContainerImageFullName(container, {});
279+
expect(result).toBe('fallback-registry/test/app:1.2.3');
280+
});
281+
282+
test('uses an @ separator when the tag override is a digest', () => {
283+
const container = makeContainer('ghcr', 'https://ghcr.io/v2', 'codeswhat/drydock', '1.0.0');
284+
const result = resolveContainerImageFullName(container, {}, 'sha256:abc123');
285+
expect(result).toBe('ghcr.io/codeswhat/drydock@sha256:abc123');
286+
});
287+
288+
test("delegates to the registry component's getImageFullName when available", () => {
289+
const container = makeContainer(
290+
'hub',
291+
'https://registry-1.docker.io/v2',
292+
'library/nginx',
293+
'1.25',
294+
);
295+
const registryState = {
296+
hub: {
297+
getImageFullName: (image: { name: string }, tag: string) =>
298+
`resolved:${image.name}:${tag}`,
299+
},
300+
};
301+
const result = resolveContainerImageFullName(container, registryState as never);
302+
expect(result).toBe('resolved:library/nginx:1.25');
303+
});
304+
});
234305
});

β€Žapp/api/container/shared.tsβ€Ž

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,16 @@ export function resolveContainerImageFullName(
132132
if (containerRegistry && typeof containerRegistry.getImageFullName === 'function') {
133133
return containerRegistry.getImageFullName(container.image, tag);
134134
}
135-
return `${container.image.registry.url}/${container.image.name}:${tag}`;
135+
// No registry component is available β€” controller mode runs registries on
136+
// the agents, not the controller. Mirror Registry.getImageFullName so
137+
// scanners receive a plain image reference: registry.url is the v2 API base
138+
// (e.g. "https://registry-1.docker.io/v2"), which Trivy would otherwise
139+
// treat as a literal host, producing "https://https/v2/" DNS failures.
140+
// See issue #374.
141+
const separator = tag.includes(':') ? '@' : ':';
142+
return `${container.image.registry.url}/${container.image.name}${separator}${tag}`
143+
.replace(/https?:\/\//, '')
144+
.replace(/\/v2/, '');
136145
}
137146

138147
export async function resolveContainerRegistryAuth(

β€Žui/src/components/containers/ContainersGroupedViews.vueβ€Ž

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -645,14 +645,15 @@ onScopeDispose(() => {
645645
<CopyableTag :tag="c.newDigest" class="container-version-tag container-version-tag-target text-2xs-plus font-semibold" style="color: var(--dd-primary);" @click.stop>{{ formatShortDigest(c.newDigest) }}</CopyableTag>
646646
</div>
647647
</div>
648+
<!-- #356 / #370 regression guard: non-digest-pinned containers (floating or
649+
specific tag with digest watch, e.g. `:latest`, `v8.13.2`) show the
650+
human-readable TAG ONLY here β€” the digest delta belongs in the tooltip.
651+
Do NOT render a visible `sha256:… β†’ sha256:…` pair in this branch; that
652+
display is for the digest-PINNED branch directly above. This bug has
653+
regressed twice (#356 fixed it, b40d3db8 re-broke it β†’ #370). -->
648654
<div v-else-if="c.updateKind === 'digest' && c.newDigest && c.currentDigest" class="container-version-query">
649-
<div class="container-version-flow flex-col items-start gap-0.5">
650-
<span class="text-2xs dd-text-muted truncate max-w-[140px]" v-tooltip.top="c.currentTag">{{ c.currentTag }}</span>
651-
<div class="container-version-flow">
652-
<span class="container-version-tag text-2xs-plus dd-text-secondary" v-tooltip.top="c.currentDigest">{{ formatShortDigest(c.currentDigest) }}</span>
653-
<AppIcon name="arrow-right" :size="8" class="container-version-arrow dd-text-muted shrink-0" />
654-
<CopyableTag :tag="c.newDigest" class="container-version-tag container-version-tag-target text-2xs-plus font-semibold" style="color: var(--dd-primary);" @click.stop>{{ formatShortDigest(c.newDigest) }}</CopyableTag>
655-
</div>
655+
<div class="container-version-flow">
656+
<CopyableTag :tag="c.currentTag" class="container-version-tag container-version-tag-target text-2xs-plus font-semibold" style="color: var(--dd-primary);" v-tooltip.top="tt(`${c.currentTag} β€” ${formatShortDigest(c.currentDigest)} β†’ ${formatShortDigest(c.newDigest)}`)" @click.stop>{{ c.currentTag }}</CopyableTag>
656657
</div>
657658
</div>
658659
<div v-else-if="c.newTag" class="container-version-query">
@@ -1059,18 +1060,18 @@ onScopeDispose(() => {
10591060
</span>
10601061
<span v-if="c.updateMaturity" class="text-2xs dd-text-muted">{{ getUpdateMaturityLabel(c.updateMaturity) }}</span>
10611062
</template>
1063+
<!-- #356 / #370 regression guard: non-digest-pinned containers (floating or
1064+
specific tag with digest watch, e.g. `:latest`, `v8.13.2`) show the
1065+
human-readable TAG ONLY here β€” the digest delta belongs in the tooltip.
1066+
Do NOT render a visible `sha256:… β†’ sha256:…` pair in this branch; that
1067+
display is for the digest-PINNED branch directly above. This bug has
1068+
regressed twice (#356 fixed it, b40d3db8 re-broke it β†’ #370). -->
10621069
<template v-else-if="c.updateKind === 'digest' && c.newDigest && c.currentDigest">
1063-
<span class="text-2xs-plus ml-1 dd-text-muted shrink-0">{{ t('containerComponents.groupedViews.latestLabel') }}</span>
1064-
<CopyableTag :tag="c.currentDigest" class="text-xs dd-text-muted truncate max-w-[80px]" @click.stop>{{ formatShortDigest(c.currentDigest) }}</CopyableTag>
1065-
<AppIcon name="arrow-right" :size="8" class="dd-text-muted shrink-0" />
1066-
<CopyableTag :tag="c.newDigest" class="text-xs font-bold truncate max-w-[80px]"
1067-
:style="{ color: updateKindColor(c.updateKind).text }" @click.stop>
1068-
{{ formatShortDigest(c.newDigest) }}
1069-
</CopyableTag>
10701070
<span
10711071
data-test="container-card-update-state"
1072-
class="inline-flex items-center gap-1.5 text-2xs-plus font-semibold"
1072+
class="inline-flex items-center gap-1.5 ml-1 text-2xs-plus font-semibold"
10731073
:style="{ color: getContainerUpdateStateColor(c) }"
1074+
v-tooltip.top="tt(`${formatShortDigest(c.currentDigest)} β†’ ${formatShortDigest(c.newDigest)}`)"
10741075
>
10751076
<span class="h-2 w-2 shrink-0 rounded-full" :style="{ backgroundColor: getContainerUpdateStateColor(c) }"></span>
10761077
{{ getContainerUpdateStateLabel(c) }}

0 commit comments

Comments
Β (0)