Repository navigation
Conversation
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
|
A preview chart for this PR is available, packaged from Digest: Preview images for this PR are published too: #1398 (comment) Verify the preview chart: |
🔒 Vulnerability Check Results
Showing 4 of 7 total findings. govulncheck exit code: 0 |
There was a problem hiding this comment.
23 issues found across 103 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts">
<violation number="1" location="src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts:141">
P2: Custom agent: **Enforce Strict Maintainability Standards**
This change takes the shared rule component from 929 to 1,227 lines, with the preview/file-filter state and workflow added inline. Extract that cohesive UI and state into a focused component or module to keep the shared form below the rule's 1,000-line threshold.</violation>
<violation number="2" location="src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts:630">
P1: Editing a rule with a file filter drops that filter on save. The new serialization updates `copyRuleForm`, but the update branch still submits raw `this.ruleForm.value`, whose FILE control is intentionally empty; submit the transformed payload for updates as well.</violation>
<violation number="3" location="src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts:1208">
P2: An in-flight preview can repopulate the form after the source or rule changes, showing results for the wrong registry. Cancel preview requests or ignore responses whose source/form generation no longer matches the request.</violation>
</file>
<file name="src/controller/replication/flow/copy_source_test.go">
<violation number="1" location="src/controller/replication/flow/copy_source_test.go:67">
P2: This test cannot detect configuring the wrong adapter or configuring after `FetchArtifacts`, because both registry sides share `adp` and the fetch expectation does not inspect `got`. Use distinct source and destination adapters, and have the source adapter’s `FetchArtifacts` callback assert that `ConfigureSource` already ran.</violation>
</file>
<file name="src/portal/src/app/base/project/repository/artifact/artifact-additions/files/files.component.ts">
<violation number="1" location="src/portal/src/app/base/project/repository/artifact/artifact-additions/files/files.component.ts:61">
P2: This checks optional `children` instead of the required `type`, so empty directories are rendered with a file-type icon because the API omits empty `children`. Use `file.type === 'directory'` to classify folders.</violation>
</file>
<file name="src/pkg/reg/adapter/huggingface/replication_test.go">
<violation number="1" location="src/pkg/reg/adapter/huggingface/replication_test.go:207">
P2: This restart check can pass without proving deterministic reconstruction: the transfer skips an existing tag when its freshly resolved digest differs and `Override` is false, leaving the old destination digest unchanged. Reset the destination before the post-cache-clear run, or compare the freshly pulled source digest directly before asserting it matches the first run.</violation>
</file>
<file name="src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.html">
<violation number="1" location="src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.html:174">
P2: This removes the existing GCR fallback when `/replication/adapterinfos` is unavailable: `isFixedAccessKey()` becomes false and the `_json_key` field is editable. Preserve the explicit GCR check alongside the metadata-driven check.</violation>
</file>
<file name="src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.spec.ts">
<violation number="1" location="src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.spec.ts:664">
P2: Use the production `huggingface` adapter type in this fixture and when assigning `comp.target.type`; `hf` is not the registered adapter key, so regressions in the real Hugging Face metadata entry could pass this test.</violation>
</file>
<file name="docs/model-sync-option-b.md">
<violation number="1" location="docs/model-sync-option-b.md:72">
P3: These test commands are not copy-pastable from the repository root: the Go and Angular workspaces live under `src`, and `<touched specs>` is shell redirection syntax. Prefix the commands with their required working directories and use concrete spec paths, or run the full portal suite.</violation>
<violation number="2" location="docs/model-sync-option-b.md:160">
P2: The preview endpoint performs source downloads synchronously in the core request. Large repositories or repeated previews can exhaust core request workers and outbound connections; move resolution to a bounded worker or enforce strict timeout, size, and rate limits before relying on this endpoint in production.</violation>
</file>
<file name="src/pkg/reg/adapter/huggingface/pull_test.go">
<violation number="1" location="src/pkg/reg/adapter/huggingface/pull_test.go:379">
P3: This test never exercises vanished-head handling because the target digest belongs to `main`, which is scanned before the vanished `v1` ref. Make the first scanned head invalid and place the target digest on a later ref so the test fails if not-found heads are not skipped.</violation>
</file>
<file name="src/pkg/reg/adapter/huggingface/hub/errors.go">
<violation number="1" location="src/pkg/reg/adapter/huggingface/hub/errors.go:57">
P2: Map HTTP 400 to `errors.BadRequestCode`; otherwise a Hub bad-request response is surfaced as `UNKNOWN` and can become a Harbor 500/internal-error response instead of a client error.</violation>
</file>
<file name="docs/model-import-foundation.md">
<violation number="1" location="docs/model-import-foundation.md:7">
P2: This says no public Resolve API is designed, but this revision publishes `POST /registries/{id}/preview` and `supported_preview` in the Swagger contract. Qualify the statement or document the generic preview endpoint here so UI consumers do not miss it.</violation>
<violation number="2" location="docs/model-import-foundation.md:103">
P2: The Resolve example ignores `ok` and calls the resolver for unsupported sources, which panics instead of returning the documented unsupported-preview error. Check `ok` before calling `Resolve`.</violation>
</file>
<file name="src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.ts">
<violation number="1" location="src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.ts:291">
P2: Selecting Hugging Face before `/replication/adapterinfos` finishes leaves the fixed `token` key blank: `adapterChange()` clears it while this helper is false, then the helper becomes true and the field becomes read-only. Initialize the selected adapter after metadata loads or defer adapter selection until both requests complete.</violation>
</file>
<file name="src/pkg/reg/adapter/huggingface/naming.go">
<violation number="1" location="src/pkg/reg/adapter/huggingface/naming.go:48">
P2: A Hugging Face ref named `sha-<12 hex>` can shadow the generated revision tag with the same name. Pulls then follow the mutable source ref instead of the intended commit, and moving that ref can make an allegedly immutable tag resolve to a different artifact; reserve this namespace or detect and handle the collision consistently when indexing and generating tags.</violation>
</file>
<file name="src/controller/replication/policy.go">
<violation number="1" location="src/controller/replication/policy.go:221">
P2: `registryInfo` is not side-effect-free: it creates the adapter, and some factories perform network discovery during `Create`. Creating or updating a policy can therefore block on registry timeouts for a source-only check that ultimately accepts errors; use static provider metadata or cached capabilities for destination validation.</violation>
</file>
<file name="src/pkg/reg/adapter/huggingface/resolve.go">
<violation number="1" location="src/pkg/reg/adapter/huggingface/resolve.go:58">
P2: Commit-pinned previews still require the Hub `/refs` endpoint, despite the documented 40-hex path skipping that call. Guard the refs lookup with `!hub.IsCommit(reference)` so pinned revisions do not spend a rate-limited request or fail solely because refs is unavailable.</violation>
</file>
<file name="src/controller/replication/model/model.go">
<violation number="1" location="src/controller/replication/model/model.go:217">
P2: This full-row mapping clears `revision_tag` whenever an older client omits the new optional field in an update. Preserve the stored value when the field is absent, or update this column only when the request explicitly supplies it.</violation>
</file>
<file name="src/pkg/reg/adapter/huggingface/hub/hubtest/hubtest.go">
<violation number="1" location="src/pkg/reg/adapter/huggingface/hub/hubtest/hubtest.go:366">
P3: The fake reports tag refs as branch refs, so any test that inspects the raw `ref` field can pass against incorrect Hub metadata. Make `refs` distinguish branch and tag collections and emit `refs/tags/` for tags.</violation>
</file>
<file name="src/portal/src/app/shared/units/file-filter.ts">
<violation number="1" location="src/portal/src/app/shared/units/file-filter.ts:61">
P2: This local matcher accepts malformed patterns that the replication API rejects. Validate file/tag patterns with the same doublestar rules before rematching, or mark the preview invalid instead of displaying results for an unsaveable rule.</violation>
</file>
<file name="src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.html">
<violation number="1" location="src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.html:232">
P2: Switching the source leaves the standalone file include/exclude values intact. Returning to another file-capable source can submit stale patterns from the previous source; reset the file-filter state whenever the source capabilities are replaced.</violation>
</file>
<file name="src/portal/src/app/base/project/repository/artifact/artifact-model-metadata/artifact-model-metadata.component.ts">
<violation number="1" location="src/portal/src/app/base/project/repository/artifact/artifact-model-metadata/artifact-model-metadata.component.ts:59">
P2: The Parameters row reads `config.paramSize`, but CNAI model metadata uses `config.parameterSize`; imported models therefore never display their parameter count. Read `c.parameterSize` instead.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| } | ||
| }); | ||
| } | ||
| copyRuleForm.filters.forEach(item => { |
There was a problem hiding this comment.
P1: Editing a rule with a file filter drops that filter on save. The new serialization updates copyRuleForm, but the update branch still submits raw this.ruleForm.value, whose FILE control is intentionally empty; submit the transformed payload for updates as well.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts, line 630:
<comment>Editing a rule with a file filter drops that filter on save. The new serialization updates `copyRuleForm`, but the update branch still submits raw `this.ruleForm.value`, whose FILE control is intentionally empty; submit the transformed payload for updates as well.</comment>
<file context>
@@ -553,6 +627,20 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
}
});
}
+ copyRuleForm.filters.forEach(item => {
+ if (item.type === FilterType.FILE) {
+ const f = this.fileFilter;
</file context>
| copyStringForLabelFilter: string = ''; | ||
| // capabilities of the selected source; every model-specific control below | ||
| // is driven by them, never by the registry type | ||
| sourceInfo: RegistryInfo; |
There was a problem hiding this comment.
P2: Custom agent: Enforce Strict Maintainability Standards
This change takes the shared rule component from 929 to 1,227 lines, with the preview/file-filter state and workflow added inline. Extract that cohesive UI and state into a focused component or module to keep the shared form below the rule's 1,000-line threshold.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts, line 141:
<comment>This change takes the shared rule component from 929 to 1,227 lines, with the preview/file-filter state and workflow added inline. Extract that cohesive UI and state into a focused component or module to keep the shared form below the rule's 1,000-line threshold.</comment>
<file context>
@@ -121,6 +136,24 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
copyStringForLabelFilter: string = '';
+ // capabilities of the selected source; every model-specific control below
+ // is driven by them, never by the registry type
+ sourceInfo: RegistryInfo;
+ sourceOnlyTypes = new Set<string>();
+ fileInclude: string = '';
</file context>
| adp := &configurableAdapter{mockAdapter: &mockAdapter{}} | ||
| factory := &mockFactory{} | ||
| factory.On("AdapterPattern").Return(nil) | ||
| factory.On("Create", mock.Anything).Return(adp, nil) |
There was a problem hiding this comment.
P2: This test cannot detect configuring the wrong adapter or configuring after FetchArtifacts, because both registry sides share adp and the fetch expectation does not inspect got. Use distinct source and destination adapters, and have the source adapter’s FetchArtifacts callback assert that ConfigureSource already ran.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/controller/replication/flow/copy_source_test.go, line 67:
<comment>This test cannot detect configuring the wrong adapter or configuring after `FetchArtifacts`, because both registry sides share `adp` and the fetch expectation does not inspect `got`. Use distinct source and destination adapters, and have the source adapter’s `FetchArtifacts` callback assert that `ConfigureSource` already ran.</comment>
<file context>
@@ -0,0 +1,95 @@
+ adp := &configurableAdapter{mockAdapter: &mockAdapter{}}
+ factory := &mockFactory{}
+ factory.On("AdapterPattern").Return(nil)
+ factory.On("Create", mock.Anything).Return(adp, nil)
+ require.NoError(t, adapter.RegisterFactory("TEST_COPY_FLOW_SOURCE_OPTIONS", factory))
+ adp.On("Info").Return(&model.RegistryInfo{SupportedResourceTypes: []string{model.ResourceTypeArtifact}}, nil)
</file context>
| } | ||
|
|
||
| iconFor(file: FilesItem & { expanded?: boolean }): string { | ||
| if (file.children) { |
There was a problem hiding this comment.
P2: This checks optional children instead of the required type, so empty directories are rendered with a file-type icon because the API omits empty children. Use file.type === 'directory' to classify folders.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/base/project/repository/artifact/artifact-additions/files/files.component.ts, line 61:
<comment>This checks optional `children` instead of the required `type`, so empty directories are rendered with a file-type icon because the API omits empty `children`. Use `file.type === 'directory'` to classify folders.</comment>
<file context>
@@ -56,6 +57,13 @@ export class ArtifactFilesComponent implements OnInit {
}
+ iconFor(file: FilesItem & { expanded?: boolean }): string {
+ if (file.children) {
+ return file.expanded ? 'folder-open' : 'folder';
+ }
</file context>
| if (file.children) { | |
| if (file.type === 'directory') { |
| .pipe(finalize(() => (this.previewing = false))) | ||
| .subscribe({ | ||
| next: res => { | ||
| this.preview = res; |
There was a problem hiding this comment.
P2: An in-flight preview can repopulate the form after the source or rule changes, showing results for the wrong registry. Cancel preview requests or ignore responses whose source/form generation no longer matches the request.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/base/left-side-nav/replication/replication/create-edit-rule/create-edit-rule.component.ts, line 1208:
<comment>An in-flight preview can repopulate the form after the source or rule changes, showing results for the wrong registry. Cancel preview requests or ignore responses whose source/form generation no longer matches the request.</comment>
<file context>
@@ -926,4 +1016,212 @@ export class CreateEditRuleComponent implements OnInit, OnDestroy {
+ .pipe(finalize(() => (this.previewing = false)))
+ .subscribe({
+ next: res => {
+ this.preview = res;
+ this.previewFor = request;
+ this.rematchPreview();
</file context>
| go test ./pkg/reg/... ./controller/replication/... ./controller/registry/ ./server/v2.0/handler/ # all ok | ||
| golangci-lint run ./controller/registry/... ./server/v2.0/handler/ ./pkg/reg/adapter/huggingface/... ./pkg/reg/model/... # 0 issues | ||
| npx ng test --watch=false --include=<touched specs> # Executed 47 of 47 SUCCESS |
There was a problem hiding this comment.
P3: These test commands are not copy-pastable from the repository root: the Go and Angular workspaces live under src, and <touched specs> is shell redirection syntax. Prefix the commands with their required working directories and use concrete spec paths, or run the full portal suite.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/model-sync-option-b.md, line 72:
<comment>These test commands are not copy-pastable from the repository root: the Go and Angular workspaces live under `src`, and `<touched specs>` is shell redirection syntax. Prefix the commands with their required working directories and use concrete spec paths, or run the full portal suite.</comment>
<file context>
@@ -0,0 +1,161 @@
+## Tests
+
+```
+go test ./pkg/reg/... ./controller/replication/... ./controller/registry/ ./server/v2.0/handler/ # all ok
+golangci-lint run ./controller/registry/... ./server/v2.0/handler/ ./pkg/reg/adapter/huggingface/... ./pkg/reg/model/... # 0 issues
+npx ng test --watch=false --include=<touched specs> # Executed 47 of 47 SUCCESS
</file context>
| go test ./pkg/reg/... ./controller/replication/... ./controller/registry/ ./server/v2.0/handler/ # all ok | |
| golangci-lint run ./controller/registry/... ./server/v2.0/handler/ ./pkg/reg/adapter/huggingface/... ./pkg/reg/model/... # 0 issues | |
| npx ng test --watch=false --include=<touched specs> # Executed 47 of 47 SUCCESS | |
| (cd src && go test ./pkg/reg/... ./controller/replication/... ./controller/registry/ ./server/v2.0/handler/) | |
| (cd src && golangci-lint run ./controller/registry/... ./server/v2.0/handler/ ./pkg/reg/adapter/huggingface/... ./pkg/reg/model/...) | |
| (cd src/portal && npx ng test --watch=false) |
| _, want := manifestOf(t, newTestAdapter(t, hubRegistry(h, 1, ""), newMemoryCache()), repoTiny, "main") | ||
|
|
||
| // v1 points at a commit the Hub no longer has; the main head is still found. | ||
| h.SetTag(model.ID, "v1", "dddddddddddddddddddddddddddddddddddddddd") |
There was a problem hiding this comment.
P3: This test never exercises vanished-head handling because the target digest belongs to main, which is scanned before the vanished v1 ref. Make the first scanned head invalid and place the target digest on a later ref so the test fails if not-found heads are not skipped.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/pkg/reg/adapter/huggingface/pull_test.go, line 379:
<comment>This test never exercises vanished-head handling because the target digest belongs to `main`, which is scanned before the vanished `v1` ref. Make the first scanned head invalid and place the target digest on a later ref so the test fails if not-found heads are not skipped.</comment>
<file context>
@@ -0,0 +1,392 @@
+ _, want := manifestOf(t, newTestAdapter(t, hubRegistry(h, 1, ""), newMemoryCache()), repoTiny, "main")
+
+ // v1 points at a commit the Hub no longer has; the main head is still found.
+ h.SetTag(model.ID, "v1", "dddddddddddddddddddddddddddddddddddddddd")
+ a := newTestAdapter(t, hubRegistry(h, 1, ""), newMemoryCache())
+ exist, _, err := a.ManifestExist(repoTiny, want.String())
</file context>
| } | ||
| sort.Strings(keys) | ||
| for _, k := range keys { | ||
| out = append(out, map[string]string{"name": k, "ref": "refs/heads/" + k, "targetCommit": names[k]}) |
There was a problem hiding this comment.
P3: The fake reports tag refs as branch refs, so any test that inspects the raw ref field can pass against incorrect Hub metadata. Make refs distinguish branch and tag collections and emit refs/tags/ for tags.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/pkg/reg/adapter/huggingface/hub/hubtest/hubtest.go, line 366:
<comment>The fake reports tag refs as branch refs, so any test that inspects the raw `ref` field can pass against incorrect Hub metadata. Make `refs` distinguish branch and tag collections and emit `refs/tags/` for tags.</comment>
<file context>
@@ -0,0 +1,396 @@
+ }
+ sort.Strings(keys)
+ for _, k := range keys {
+ out = append(out, map[string]string{"name": k, "ref": "refs/heads/" + k, "targetCommit": names[k]})
+ }
+ return out
</file context>
|
Warning Outdated: the preview image build for Preview images for this PR are available in
A preview chart for this PR is published too, and its comment carries the install command: #1398 (comment) Verify a preview image: Verify the SBOM attestation: |
b76e336 to
71feb9d
Compare
Signed-off-by: Vad1mo <vadim@8gears.com>
71feb9d to
9d7dd69
Compare
Add a `huggingface` registry type that lets pull replication and proxy cache copy Hugging Face Hub models into Harbor as ModelPack model-spec artifacts, without a dedicated sync framework. The adapter synthesizes a byte-deterministic OCI manifest from the Hub API for a pinned commit: one raw layer per file, LFS sha256 reused as the layer digest, non-LFS files hashed and checked against their git blob id, file classification frozen from modctl. The same commit always yields the same digest, so replication's digest-equality skip and proxy cache's local/remote comparison work unchanged. Digest-to-file lookups go through lib/cache scoped per registry (Redis in core, in-process memory in jobservice) with a stateless rebuild fallback, because proxy cache creates a new adapter per request. The type is source-only: replication policies with a Hugging Face destination are rejected. Refs: goharbor/community#297 Signed-off-by: Vadim Bauer <vb@container-registry.com> Signed-off-by: Vad1mo <vadim@8gears.com>
…s, propagate lookup errors Signed-off-by: Vadim Bauer <vadim@8gears.com> Signed-off-by: Vad1mo <vadim@8gears.com>
Add a generic "file" replication filter that limits which files a source adapter packs into an artifact. The policy's filter becomes model.SourceOptions, which the copy flow hands to the source adapter through the optional adapter.SourceConfigurer interface and serializes with the source resource, so the transfer configures its own adapter the same way. Adapters that do not implement the interface fail instead of ignoring the options. The Hugging Face adapter selects files with the filter before hashing and synthesis. The digest depends on the commit and the selected files only, so the same filter always yields the same digest and a filter that drops files yields a different one. Signed-off-by: Vad1mo <vadim@8gears.com>
Add the optional adapter.ArtifactResolver interface. Resolve reports what a copy of one source reference would produce without copying: the revision, the moving tags pointing at it, license, task, the file list with class, size, sha256 and whether the file filter selects it, matched totals, and the manifest digest of the copy. The Hugging Face adapter implements it with the same snapshot, hashing and synthesis code as the copy, so the previewed digest is the copied digest. The Hub's pipeline_tag is read into the snapshot as the task. A live test behind the hubintegration build tag resolves against the public Hub. Signed-off-by: Vad1mo <vadim@8gears.com>
Add a revision_tag flag to replication policies. With it, every copy also gets the immutable tag sha-<first 12 hex of the source revision> next to its moving tags, so a scheduled pull of main keeps every imported revision addressable. The flag travels with the other source options to the source adapter; policy validation accepts it only for sources whose registry info declares supported_revision_tag. The Hugging Face adapter appends the revision tag after the tag filter and resolves a revision tag back to the ref head it names. The column is added idempotently in harbor_next.sql. Signed-off-by: Vad1mo <vadim@8gears.com>
Add source_only to the registry info. Policy validation now rejects any registry that declares it as a replication destination instead of checking for the Hugging Face type, and the registry info API returns the flag so a UI can hide such registries from destination pickers. The local Harbor is never probed, and a registry whose capabilities cannot be read is accepted as before. Signed-off-by: Vad1mo <vadim@8gears.com>
Document the Go interfaces, REST fields and database column that the model import UI prototypes build on, how the file filter and revision tag travel from the policy to the source adapter, their semantics, and the end-to-end verification on the dev stack. Signed-off-by: Vad1mo <vadim@8gears.com>
…y Info
POST /registries/{id}/preview resolves what a copy of a repository reference
with an optional file filter would produce, for any adapter that implements
adapter.ArtifactResolver; others answer 400. RegistryInfo.supported_preview is
derived from that interface, so adapters declare nothing new.
FilterStyle gains optional label, placeholder and description so a source can
name its filters in its own terms, and the adapter pattern gains source_only so
clients can leave such registries out of destination pickers without creating
an adapter per registry. The Hugging Face adapter uses both and fixes its access
key, so the endpoint form only asks for the token.
Signed-off-by: Vad1mo <vadim@8gears.com>
Signed-off-by: Vad1mo <vadim@8gears.com>
The form reads everything model-specific from the source's registry info, never from the registry type: the file filter gets include/exclude inputs when the source lists the file filter, the revision tag option appears when the source declares supported_revision_tag, a preview panel appears when it declares supported_preview, and filters show the adapter's own label, placeholder and help. Registries whose type is source_only are left out of the destination picker. Sources without these declarations render exactly as before. Signed-off-by: Vad1mo <vadim@8gears.com>
…attern A fixed access key (AccessKeyTypeFix) is prefilled and read-only, and the secret is a text area only for AccessSecretTypeFile, instead of checking for the GCR type. Hugging Face endpoints thus only ask for a token. Source-only providers say so under the provider select. Signed-off-by: Vad1mo <vadim@8gears.com>
A Model section lists licenses, source URL, source revision and the model-spec config (architecture, format, parameters, precision, quantization) instead of the raw descriptor/config/modelfs JSON in the overview. The Files tab picks icons by file class, the artifact list marks models with a badge, and the quantization label no longer reads the misspelled config.puantization key. Signed-off-by: Vad1mo <vadim@8gears.com>
Signed-off-by: Vad1mo <vadim@8gears.com>
There was a problem hiding this comment.
3 issues found across 33 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/portal/src/app/shared/units/file-filter.ts">
<violation number="1" location="src/portal/src/app/shared/units/file-filter.ts:62">
P2: The new safe matcher still disagrees with the backend for valid escaped doublestar patterns such as `a\*`: the local preview does not mark files that the replication backend will import. Make the glob conversion escape-aware, or use matching semantics shared with the backend.</violation>
<violation number="2" location="src/portal/src/app/shared/units/file-filter.ts:68">
P2: The client validator omits the backend limit of 64 combined include/exclude patterns. With 65 valid patterns, preview and save remain enabled but the API rejects the request; enforce the aggregate limit client-side.</violation>
</file>
<file name="src/portal/src/i18n/lang/de-de-lang.json">
<violation number="1" location="src/portal/src/i18n/lang/de-de-lang.json:703">
P3: The German locale leaves this new validation message in English. Translate it so German users see a localized error, for example `Ungültiges Dateimuster: {{patterns}}`.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| const base = path.split('/').pop(); | ||
| return patterns.some(p => { | ||
| const re = safeGlobToRegExp(p); | ||
| return !!re && re.test(p.includes('/') ? path : base); |
There was a problem hiding this comment.
P2: The new safe matcher still disagrees with the backend for valid escaped doublestar patterns such as a\*: the local preview does not mark files that the replication backend will import. Make the glob conversion escape-aware, or use matching semantics shared with the backend.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/shared/units/file-filter.ts, line 62:
<comment>The new safe matcher still disagrees with the backend for valid escaped doublestar patterns such as `a\*`: the local preview does not mark files that the replication backend will import. Make the glob conversion escape-aware, or use matching semantics shared with the backend.</comment>
<file context>
@@ -57,11 +57,67 @@ export function matchFileFilter(f: FileFilterValue, path: string): boolean {
- globToRegExp(p).test(p.includes('/') ? path : base)
+ return patterns.some(p => {
+ const re = safeGlobToRegExp(p);
+ return !!re && re.test(p.includes('/') ? path : base);
+ });
+}
</file context>
| export function invalidFilePatterns(f: FileFilterValue): string[] { | ||
| return [...(f?.include || []), ...(f?.exclude || [])].filter( | ||
| p => p.startsWith('/') || !isValidGlob(p) || !safeGlobToRegExp(p) | ||
| ); | ||
| } |
There was a problem hiding this comment.
P2: The client validator omits the backend limit of 64 combined include/exclude patterns. With 65 valid patterns, preview and save remain enabled but the API rejects the request; enforce the aggregate limit client-side.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/shared/units/file-filter.ts, line 68:
<comment>The client validator omits the backend limit of 64 combined include/exclude patterns. With 65 valid patterns, preview and save remain enabled but the API rejects the request; enforce the aggregate limit client-side.</comment>
<file context>
@@ -57,11 +57,67 @@ export function matchFileFilter(f: FileFilterValue, path: string): boolean {
+
+// Mirrors pkg/reg/model.FileFilter.Validate: the patterns the replication API
+// rejects, so a preview is never re-matched with a filter that cannot be saved.
+export function invalidFilePatterns(f: FileFilterValue): string[] {
+ return [...(f?.include || []), ...(f?.exclude || [])].filter(
+ p => p.startsWith('/') || !isValidGlob(p) || !safeGlobToRegExp(p)
</file context>
| export function invalidFilePatterns(f: FileFilterValue): string[] { | |
| return [...(f?.include || []), ...(f?.exclude || [])].filter( | |
| p => p.startsWith('/') || !isValidGlob(p) || !safeGlobToRegExp(p) | |
| ); | |
| } | |
| export function invalidFilePatterns(f: FileFilterValue): string[] { | |
| const patterns = [...(f?.include || []), ...(f?.exclude || [])]; | |
| return patterns.length > 64 | |
| ? patterns | |
| : patterns.filter( | |
| p => p.startsWith('/') || !isValidGlob(p) || !safeGlobToRegExp(p) | |
| ); | |
| } |
| "PREVIEW_FILTER_CHANGED": "File filter changed, preview again to compute the digest", | ||
| "PREVIEW_ALL_FILES": "Show all files", | ||
| "PREVIEW_MATCHED_ONLY": "Show matched files only", | ||
| "FILE_FILTER_INVALID": "Invalid file pattern: {{patterns}}", |
There was a problem hiding this comment.
P3: The German locale leaves this new validation message in English. Translate it so German users see a localized error, for example Ungültiges Dateimuster: {{patterns}}.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/i18n/lang/de-de-lang.json, line 703:
<comment>The German locale leaves this new validation message in English. Translate it so German users see a localized error, for example `Ungültiges Dateimuster: {{patterns}}`.</comment>
<file context>
@@ -700,6 +700,7 @@
"PREVIEW_FILTER_CHANGED": "File filter changed, preview again to compute the digest",
"PREVIEW_ALL_FILES": "Show all files",
"PREVIEW_MATCHED_ONLY": "Show matched files only",
+ "FILE_FILTER_INVALID": "Invalid file pattern: {{patterns}}",
"REVISION_TAG": "Also tag each copy with its source revision (sha-…)",
"REVISION_TAG_TIP": "Adds the immutable tag sha-<first 12 hex characters of the source revision> next to the moving tags, so every copied revision stays addressable after the branch moves on.",
</file context>
| "FILE_FILTER_INVALID": "Invalid file pattern: {{patterns}}", | |
| "FILE_FILTER_INVALID": "Ungültiges Dateimuster: {{patterns}}", |
…ture Register the huggingface registry type as the optional feature huggingface_model_import (config key enable_huggingface_model_import, env HARBOR_ENABLE_HUGGINGFACE_MODEL_IMPORT), off by default. While it is off, core hides the type from the adapter lists and rejects creating, updating, pinging, previewing and reading info of such a registry, proxy cache projects and replication policies that use one, and starting their replications. Proxy cache projects of the type serve only cached content. Disabling is refused while registries of the type exist. Signed-off-by: Vad1mo <vadim@8gears.com>
9d7dd69 to
99f1cd6
Compare
There was a problem hiding this comment.
8 issues found across 37 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/portal/src/app/base/left-side-nav/config/features/features.component.html">
<violation number="1" location="src/portal/src/app/base/left-side-nav/config/features/features.component.html:3">
P2: The info tooltip trigger sits inside the checkbox label. Clicking the info icon also activates the associated toggle, changing the feature when the user only intended to read its description; move the tooltip outside the label.</violation>
</file>
<file name="src/portal/src/i18n/lang/de-de-lang.json">
<violation number="1" location="src/portal/src/i18n/lang/de-de-lang.json:864">
P2: These new entries leave the entire feature-settings block in English when users select German. Translate the labels, warning, locked-by text, and Hugging Face description so the new settings follow the existing German locale behavior.
(Based on your team's feedback about German locale translations.) .</violation>
</file>
<file name="src/portal/src/app/base/left-side-nav/config/features/features.component.scss">
<violation number="1" location="src/portal/src/app/base/left-side-nav/config/features/features.component.scss:31">
P2: The beta warning becomes difficult to read in dark mode: `#aa4500` has only about 2.5:1 contrast against the dark theme background. Use a theme-aware warning color or add a dark-theme override that meets the contrast requirement.</violation>
</file>
<file name="docs/model-sync-option-b.md">
<violation number="1" location="docs/model-sync-option-b.md:169">
P2: This bullet documents behavior that the implementation does not provide: disabling the feature does not stop existing Hugging Face proxy-cache requests from contacting the upstream registry. Add a feature check to the proxy request path before constructing or using the remote helper, or remove this guarantee from the documentation.</violation>
</file>
<file name="src/controller/config/controller.go">
<violation number="1" location="src/controller/config/controller.go:120">
P1: This rejects valid bootstrap configuration when `HARBOR_ENABLE_HUGGINGFACE_MODEL_IMPORT` and `CONFIG_OVERWRITE_JSON` both contain the feature key, causing core to fail startup. Skip environment-controlled feature keys during the overwrite bootstrap (or use a separate bootstrap validation path) so the environment value wins without rejecting the initial settings.</violation>
</file>
<file name="src/portal/src/app/base/left-side-nav/config/features/features.component.ts">
<violation number="1" location="src/portal/src/app/base/left-side-nav/config/features/features.component.ts:83">
P2: The page becomes interactive before the post-save configuration refresh completes. A second edit can race that refresh and be overwritten when `updateConfig()` replaces the current configuration; include `config.getLoadingConfigStatus()` in the page's `inProgress` state, as the other configuration pages do.</violation>
</file>
<file name="src/pkg/feature/feature.go">
<violation number="1" location="src/pkg/feature/feature.go:163">
P2: The guard’s count check is not atomic with registry creation. A concurrent create can pass `CheckTypeEnabled` after the count returns zero and commit after this update, leaving a persisted endpoint while the feature is disabled; serialize these operations or recheck under a shared transaction or lock.</violation>
</file>
<file name="src/server/middleware/repoproxy/proxy.go">
<violation number="1" location="src/server/middleware/repoproxy/proxy.go:418">
P2: The feature gate does not cover the referrer proxy path: `ProxyReferrerMiddleware` bypasses `canProxy` and can still fetch referrers from a healthy disabled-type registry. Apply the same registry-type check before creating the remote helper or in a shared proxy guard.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| func (c *controller) validateCfg(ctx context.Context, cfgs map[string]any) error { | ||
| mgr := config.GetCfgManager(ctx) | ||
|
|
||
| if err := feature.ValidateConfigUpdate(ctx, cfgs); err != nil { |
There was a problem hiding this comment.
P1: This rejects valid bootstrap configuration when HARBOR_ENABLE_HUGGINGFACE_MODEL_IMPORT and CONFIG_OVERWRITE_JSON both contain the feature key, causing core to fail startup. Skip environment-controlled feature keys during the overwrite bootstrap (or use a separate bootstrap validation path) so the environment value wins without rejecting the initial settings.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/controller/config/controller.go, line 120:
<comment>This rejects valid bootstrap configuration when `HARBOR_ENABLE_HUGGINGFACE_MODEL_IMPORT` and `CONFIG_OVERWRITE_JSON` both contain the feature key, causing core to fail startup. Skip environment-controlled feature keys during the overwrite bootstrap (or use a separate bootstrap validation path) so the environment value wins without rejecting the initial settings.</comment>
<file context>
@@ -116,6 +117,9 @@ func (c *controller) updateLogEndpoint(ctx context.Context, cfgs map[string]any)
func (c *controller) validateCfg(ctx context.Context, cfgs map[string]any) error {
mgr := config.GetCfgManager(ctx)
+ if err := feature.ValidateConfigUpdate(ctx, cfgs); err != nil {
+ return errors.BadRequestError(err)
+ }
</file context>
| @@ -0,0 +1,86 @@ | |||
| <ng-template #toggleList let-features> | |||
| <clr-toggle-container *ngFor="let feature of features"> | |||
| <label [for]="feature.configKey"> | |||
There was a problem hiding this comment.
P2: The info tooltip trigger sits inside the checkbox label. Clicking the info icon also activates the associated toggle, changing the feature when the user only intended to read its description; move the tooltip outside the label.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/base/left-side-nav/config/features/features.component.html, line 3:
<comment>The info tooltip trigger sits inside the checkbox label. Clicking the info icon also activates the associated toggle, changing the feature when the user only intended to read its description; move the tooltip outside the label.</comment>
<file context>
@@ -0,0 +1,86 @@
+<ng-template #toggleList let-features>
+ <clr-toggle-container *ngFor="let feature of features">
+ <label [for]="feature.configKey">
+ {{ feature.name | translate }}
+ <clr-tooltip *ngIf="feature.beta">
</file context>
| "FEATURES": "Features", | ||
| "OPTIONAL_FEATURES": "Optional Features", | ||
| "FEATURE_BETA": "Beta", | ||
| "FEATURE_BETA_WARNING": "Beta feature: behavior and stored data formats may still change between releases.", | ||
| "FEATURE_LOCKED_BY": "Set by {{source}}", | ||
| "FEATURE_HUGGINGFACE_MODEL_IMPORT": "Hugging Face Model Import", | ||
| "FEATURE_HUGGINGFACE_MODEL_IMPORT_INFO": "Adds the Hugging Face registry type, so replication rules and proxy cache projects can import Hugging Face models as OCI artifacts. It cannot be turned off while Hugging Face registry endpoints exist.", |
There was a problem hiding this comment.
P2: These new entries leave the entire feature-settings block in English when users select German. Translate the labels, warning, locked-by text, and Hugging Face description so the new settings follow the existing German locale behavior.
(Based on your team's feedback about German locale translations.) .
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/i18n/lang/de-de-lang.json, line 864:
<comment>These new entries leave the entire feature-settings block in English when users select German. Translate the labels, warning, locked-by text, and Hugging Face description so the new settings follow the existing German locale behavior.
(Based on your team's feedback about German locale translations.) .</comment>
<file context>
@@ -861,6 +861,13 @@
"ROOT_CERT_LINK": "Download",
"REGISTRY_CERTIFICATE": "Registry Zertifikat",
"NO_CHANGE": "Speichern abgebrochen, da nichts verändert wurde",
+ "FEATURES": "Features",
+ "OPTIONAL_FEATURES": "Optional Features",
+ "FEATURE_BETA": "Beta",
</file context>
| "FEATURES": "Features", | |
| "OPTIONAL_FEATURES": "Optional Features", | |
| "FEATURE_BETA": "Beta", | |
| "FEATURE_BETA_WARNING": "Beta feature: behavior and stored data formats may still change between releases.", | |
| "FEATURE_LOCKED_BY": "Set by {{source}}", | |
| "FEATURE_HUGGINGFACE_MODEL_IMPORT": "Hugging Face Model Import", | |
| "FEATURE_HUGGINGFACE_MODEL_IMPORT_INFO": "Adds the Hugging Face registry type, so replication rules and proxy cache projects can import Hugging Face models as OCI artifacts. It cannot be turned off while Hugging Face registry endpoints exist.", | |
| "FEATURES": "Funktionen", | |
| "OPTIONAL_FEATURES": "Optionale Funktionen", | |
| "FEATURE_BETA": "Beta", | |
| "FEATURE_BETA_WARNING": "Beta-Funktion: Verhalten und gespeicherte Datenformate können sich zwischen Versionen noch ändern.", | |
| "FEATURE_LOCKED_BY": "Festgelegt durch {{source}}", | |
| "FEATURE_HUGGINGFACE_MODEL_IMPORT": "Hugging-Face-Modellimport", | |
| "FEATURE_HUGGINGFACE_MODEL_IMPORT_INFO": "Fügt den Registry-Typ Hugging Face hinzu, damit Replikationsregeln und Proxy-Cache-Projekte Hugging-Face-Modelle als OCI-Artefakte importieren können. Die Funktion kann nicht deaktiviert werden, solange Hugging-Face-Registry-Endpunkte vorhanden sind.", |
| } | ||
|
|
||
| .beta-warning { | ||
| color: #aa4500; |
There was a problem hiding this comment.
P2: The beta warning becomes difficult to read in dark mode: #aa4500 has only about 2.5:1 contrast against the dark theme background. Use a theme-aware warning color or add a dark-theme override that meets the contrast requirement.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/base/left-side-nav/config/features/features.component.scss, line 31:
<comment>The beta warning becomes difficult to read in dark mode: `#aa4500` has only about 2.5:1 contrast against the dark theme background. Use a theme-aware warning color or add a dark-theme override that meets the contrast requirement.</comment>
<file context>
@@ -0,0 +1,36 @@
+}
+
+.beta-warning {
+ color: #aa4500;
+}
+
</file context>
| for one, to creating a proxy cache project on one and to creating or updating a replication | ||
| policy that uses one | ||
| - refuses to start a replication of such a policy, manual or scheduled | ||
| - serves a `huggingface` proxy cache project from its local cache only, without upstream calls |
There was a problem hiding this comment.
P2: This bullet documents behavior that the implementation does not provide: disabling the feature does not stop existing Hugging Face proxy-cache requests from contacting the upstream registry. Add a feature check to the proxy request path before constructing or using the remote helper, or remove this guarantee from the documentation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/model-sync-option-b.md, line 169:
<comment>This bullet documents behavior that the implementation does not provide: disabling the feature does not stop existing Hugging Face proxy-cache requests from contacting the upstream registry. Add a feature check to the proxy request path before constructing or using the remote helper, or remove this guarantee from the documentation.</comment>
<file context>
@@ -153,6 +153,28 @@ Browser login over plain HTTP needed no change: this base no longer uses gorilla
+ for one, to creating a proxy cache project on one and to creating or updating a replication
+ policy that uses one
+- refuses to start a replication of such a policy, manual or scheduled
+- serves a `huggingface` proxy cache project from its local cache only, without upstream calls
+
+Switching it off is refused while `huggingface` registries exist. Policies and proxy cache
</file context>
| this.configService.saveConfiguration(changes).subscribe({ | ||
| next: () => { | ||
| this.inProgress = false; | ||
| this.config.updateConfig(); |
There was a problem hiding this comment.
P2: The page becomes interactive before the post-save configuration refresh completes. A second edit can race that refresh and be overwritten when updateConfig() replaces the current configuration; include config.getLoadingConfigStatus() in the page's inProgress state, as the other configuration pages do.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/portal/src/app/base/left-side-nav/config/features/features.component.ts, line 83:
<comment>The page becomes interactive before the post-save configuration refresh completes. A second edit can race that refresh and be overwritten when `updateConfig()` replaces the current configuration; include `config.getLoadingConfigStatus()` in the page's `inProgress` state, as the other configuration pages do.</comment>
<file context>
@@ -0,0 +1,113 @@
+ this.configService.saveConfiguration(changes).subscribe({
+ next: () => {
+ this.inProgress = false;
+ this.config.updateConfig();
+ this.messageHandler.showSuccess('CONFIG.SAVE_SUCCESS');
+ },
</file context>
| mu.RLock() | ||
| guard := guards[definition.Feature] | ||
| mu.RUnlock() | ||
| if guard != nil { |
There was a problem hiding this comment.
P2: The guard’s count check is not atomic with registry creation. A concurrent create can pass CheckTypeEnabled after the count returns zero and commit after this update, leaving a persisted endpoint while the feature is disabled; serialize these operations or recheck under a shared transaction or lock.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/pkg/feature/feature.go, line 163:
<comment>The guard’s count check is not atomic with registry creation. A concurrent create can pass `CheckTypeEnabled` after the count returns zero and commit after this update, leaving a persisted endpoint while the feature is disabled; serialize these operations or recheck under a shared transaction or lock.</comment>
<file context>
@@ -0,0 +1,181 @@
+ mu.RLock()
+ guard := guards[definition.Feature]
+ mu.RUnlock()
+ if guard != nil {
+ if err := guard(ctx); err != nil {
+ return err
</file context>
| log.Errorf("failed to get registry, error:%v", err) | ||
| return false | ||
| } | ||
| if !feature.RegistryTypeEnabled(ctx, reg.Type) { |
There was a problem hiding this comment.
P2: The feature gate does not cover the referrer proxy path: ProxyReferrerMiddleware bypasses canProxy and can still fetch referrers from a healthy disabled-type registry. Apply the same registry-type check before creating the remote helper or in a shared proxy guard.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/server/middleware/repoproxy/proxy.go, line 418:
<comment>The feature gate does not cover the referrer proxy path: `ProxyReferrerMiddleware` bypasses `canProxy` and can still fetch referrers from a healthy disabled-type registry. Apply the same registry-type check before creating the remote helper or in a shared proxy guard.</comment>
<file context>
@@ -414,6 +415,10 @@ func canProxy(ctx context.Context, p *proModels.Project) bool {
log.Errorf("failed to get registry, error:%v", err)
return false
}
+ if !feature.RegistryTypeEnabled(ctx, reg.Type) {
+ log.Debugf("the registry type %s is disabled, serve project %s from the local cache only", reg.Type, p.Name)
+ return false
</file context>
Draft, for preview images and testing. Harbor-next build of Option B from the model import comparison (goharbor/community#297, upstream draft goharbor/harbor#24319).
What it contains
huggingfaceregistry adapter for pull replication and proxy cache (models become CNAI model-spec artifacts, deterministic digests)filefilter with include/exclude,ArtifactResolvercopy preview, optionalsha-<12>revision tag,source_onlyin registry infoPOST /registries/{id}/preview; replication rule form driven by source capabilities (Model / Revision / Files fields, preview panel, revision tag checkbox, source-only registries hidden as push destinations); token-only Hugging Face endpoint; Model panel on CNAI artifactsVerified
HuggingFaceTB/SmolLM2-135M@main, include*.json, revision tag on:sha256:2dc8fb68cff37ff1…, tagsmainandsha-93efa2f097d5; rerun skipped. Unfiltered copysha256:2b6f786b…(same digest as #24008). Go tests on touched packages, golangci-lint clean, touched karma specs pass.Known limitations
docs/model-import-foundation.md,docs/model-sync-option-b.md) that are prototype notesScreenshots
B1: Hugging Face endpoint, token only
B2: Rule form with Hugging Face source: Model, Revision, Files
B3: Preview inside the rule form
B4: Rule form with Docker Hub source, unchanged
B5: Executions succeeded
B6: Model artifact with the Model panel
Release Notes
Import AI models from Hugging Face into Harbor with pull replication or proxy cache. Replication rules for model sources can preview the files before import, filter files with include/exclude patterns, and add an immutable
sha-<commit>tag next to the branch tag. Model artifacts show license, source and source revision.