feat: lockfile-driven reproducible installs for Artifactory proxies#401
Open
chkp-roniz wants to merge 2 commits intomicrosoft:mainfrom
Open
feat: lockfile-driven reproducible installs for Artifactory proxies#401chkp-roniz wants to merge 2 commits intomicrosoft:mainfrom
chkp-roniz wants to merge 2 commits intomicrosoft:mainfrom
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR makes installs reproducible in Artifactory-proxied / air-gapped environments by treating the lockfile as the source of truth for dependency provenance (host), and tightening ARTIFACTORY_ONLY enforcement.
Changes:
- Record the actual resolved download host in the lockfile (including Artifactory proxy path) and prefer that host during re-installs.
- Add
ARTIFACTORY_ONLYlockfile conflict detection and prevent “direct source” cached reuse underARTIFACTORY_ONLY. - Close
ARTIFACTORY_ONLYenforcement gaps for virtual packages; add unit tests and changelog entries.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/apm_cli/commands/install.py |
Uses lockfile host for transitive downloads, adds ARTIFACTORY_ONLY conflict detection + cache enforcement, improves output. |
src/apm_cli/deps/github_downloader.py |
Adds get_resolved_host() and enforces ARTIFACTORY_ONLY for virtual file/collection/subdir packages. |
src/apm_cli/deps/lockfile.py |
Adds host_override plumbing so lockfile can store resolved hosts; backward-compatible tuple parsing. |
src/apm_cli/drift.py |
Makes build_download_ref() prefer lockfile host when rebuilding download refs. |
tests/unit/test_artifactory_support.py |
Adds unit tests for lockfile host override, build_download_ref host preference, and conflict detection logic. |
CHANGELOG.md |
Adds Unreleased entries describing the new behavior. |
…ackages The lockfile now records the actual download host (including Artifactory proxy path) so that subsequent installs fetch from the exact same source without requiring ARTIFACTORY_BASE_URL to be set. This makes the lockfile the single source of truth for package provenance. Key changes: - Lockfile host field stores the resolved proxy host+path (e.g. art.example.com/artifactory/apm) instead of the original github.com - build_download_ref prefers lockfile host over manifest host - ARTIFACTORY_ONLY conflict detection: hard error when lockfile has github.com deps but ARTIFACTORY_ONLY=1 is set - ARTIFACTORY_ONLY enforcement for all virtual package types (files, collections, subdirectories) — closes a gap where subdirectory packages bypassed the check and fell through to direct git clone - Public get_resolved_host() API on GitHubPackageDownloader Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
e1e669b to
f705aaf
Compare
- build_download_ref preserves locked_dep.resolved_ref when no commit SHA is available (Artifactory downloads) - Add tests for no-commit + pinned ref path and host-only override - Update authentication docs with lockfile reproducibility section - CHANGELOG entries include PR number (microsoft#401) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR enhances the Artifactory VCS support added in #354 to make the lockfile the single source of truth for package provenance — ensuring reproducible, auditable installs in enterprise and air-gapped environments.
Why This Matters
The Lockfile Integrity Problem
PR #354 introduced JFrog Artifactory as a first-class package source. However, when a package was installed through an Artifactory proxy, the lockfile recorded
host: github.com— the original host, not the actual download source. This created several problems:Broken reproducibility: A developer installs via Artifactory with
ARTIFACTORY_BASE_URL. They commit the lockfile. A colleague runsapm install— but the lockfile saysgithub.com, so APM tries to fetch directly from GitHub instead of Artifactory. In an air-gapped network, this fails silently or unexpectedly.Supply chain opacity: The lockfile couldn't answer "where did this package actually come from?" — a critical audit question in regulated environments. A package fetched through a corporate-approved proxy was indistinguishable from one fetched directly from the internet.
Air-gap leaks: With
ARTIFACTORY_ONLY=1, virtual subdirectory packages (e.g.,github/awesome-copilot/skills/review-and-refactor) bypassed the enforcement check and fell through to direct git clone — breaking the air-gap guarantee.Stale lockfile ambiguity: If a team transitions from direct GitHub access to Artifactory-only, there was no detection of the mismatch between the lockfile (locked to
github.com) and the new policy (ARTIFACTORY_ONLY=1). Installs would fail with confusing downloader errors instead of a clear remediation path.The Principle
The lockfile must be self-contained. It should capture everything needed to reproduce the exact same install — including where each package was fetched from. No environment variables should be required for a lockfile-driven reinstall. This is the same principle that makes
package-lock.json,Cargo.lock, andpoetry.lockreliable in their ecosystems.Changes
1. Lockfile records actual download host (
lockfile.py,install.py)When a package is installed through an Artifactory proxy, the lockfile now stores the full proxy path in the
hostfield:The
hostfield storeshostname/repo-pathso that{host}/{repo_url}reconstructs the full download URL. Therepo_urlremains unchanged (owner/repo) for consistent identity and key matching.2. Lockfile host drives re-installs (
drift.py)build_download_ref()now prefers the lockfile'shostover the manifest'sdep_ref.host. This means:dep_ref.host+ env var routing (existing behavior)--update: Ignores lockfile, re-resolves from manifest (existing behavior)This also applies to the transitive dependency download callback in
install.py.3. ARTIFACTORY_ONLY conflict detection (
install.py)When
ARTIFACTORY_ONLY=1is set but the lockfile contains dependencies locked togithub.com, APM now exits with a clear error:Additionally, cached packages with
github.comin the lockfile are not silently reused whenARTIFACTORY_ONLYis active — they are forced through the download path.4. ARTIFACTORY_ONLY enforcement for virtual packages (
github_downloader.py)Closes a gap in #354 where virtual file, collection, and subdirectory packages bypassed the
ARTIFACTORY_ONLYcheck:is_virtual_file()— now blocked whenARTIFACTORY_ONLYis set without a proxyis_virtual_collection()— sameis_virtual_subdirectory()— already had partial handling, now also blocks when proxy is unavailable5. Public
get_resolved_host()API (github_downloader.py)New public method on
GitHubPackageDownloaderthat returns the actual download host for a dependency (e.g., the Artifactory proxy path). This replaces direct access to private_parse_artifactory_base_url()/_should_use_artifactory_proxy()frominstall.py.Files Changed
src/apm_cli/commands/install.pysrc/apm_cli/deps/github_downloader.pyget_resolved_host()API, virtual package ARTIFACTORY_ONLY enforcementsrc/apm_cli/deps/lockfile.pyhost_overrideparam infrom_dependency_refandfrom_installed_packagessrc/apm_cli/drift.pybuild_download_refprefers lockfile host, works withoutresolved_committests/unit/test_artifactory_support.pyCHANGELOG.mdTest Plan
ARTIFACTORY_BASE_URL→ lockfile records Artifactory hostapm_modules, runapm installwithout env vars → fetches from Artifactory using lockfile hostgithub.com+ARTIFACTORY_ONLY=1→ clear error with remediationARTIFACTORY_ONLY=1withoutARTIFACTORY_BASE_URL→ virtual subdirectory packages blocked (not silently cloned)github.com, no regressionsinstalled_packagestuples still work, older APM can read new lockfilesBackward Compatibility
hostfield can now containhostname/pathvalues (e.g.,art.example.com/artifactory/apm). Older APM versions read this as a plain string —LockedDependency.from_dict()stores whatever value is there. The field is not used for path computation, so older versions are unaffected.from_installed_packages()accepts both 4-element and 5-element tuples viaentry[:4]/len(entry) > 4pattern.🤖 Generated with Claude Code