Skip to content

RTECO-1782: JFROG_RUN_NATIVE wins over project config, fix dotnet help, add FlexPack tests - #3699

Closed
bhanurp wants to merge 7 commits into
jfrog:masterfrom
bhanurp:RTECO-1782
Closed

RTECO-1782: JFROG_RUN_NATIVE wins over project config, fix dotnet help, add FlexPack tests#3699
bhanurp wants to merge 7 commits into
jfrog:masterfrom
bhanurp:RTECO-1782

Conversation

@bhanurp

@bhanurp bhanurp commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

What

Two commits: a behaviour fix in the FlexPack gate with the help text it depends on, and the dotnet FlexPack integration suite.


1. JFROG_RUN_NATIVE now wins over a project config file

The gate was:

if artutils.ShouldRunNative(configFilePath) && !configExists {

so any .jfrog/projects/{dotnet,nuget}.yaml in a project forced the legacy path even with JFROG_RUN_NATIVE=true. Nothing was logged.

Worse, the legacy path doesn't recognise the native-only flags, so it forwarded them to MSBuild:

MSBUILD : error MSB1001: Unknown switch.
Switch: --repo-resolve

— which names an MSBuild flag and gives no hint that a YAML file two directories down is the cause. And if the stale config happened to be valid, the build silently resolved from whatever repository that file named rather than the --repo-resolve on the command line. Wrong-repo resolution, no diagnostic.

Now the env var takes precedence and the config is reported and ignored:

[Warn] JFROG_RUN_NATIVE=true, so the dotnet configuration at ".../dotnet.yaml" is being
       ignored and the command runs in native (FlexPack) mode. Unset JFROG_RUN_NATIVE to
       use the legacy 'jf dotnet-config' path.

Applied to both DotnetCmd and NugetCmd — identical gate, identical failure.

Regression-verified, all three paths:

Setup Behaviour
JFROG_RUN_NATIVE=true + config present warns, runs FlexPack ✅
env unset + config present legacy path still works ✅
env unset + no config original "run jf dotnet-config first" error ✅

The help text was the other half

jf dotnet --help listed jf dotnet-config as a prerequisite — "must be run first". So a user following the CLI's own documentation created exactly the file that disabled FlexPack. That command is out of scope for FlexPack per the spec, yet remained the documented happy path. Now described as optional under JFROG_RUN_NATIVE, for both dotnet and nuget.

Also corrected the sub-command list, which claimed (restore, build, pack, push). There is no jf dotnet push — the real command is the two-token jf dotnet nuget push, fully implemented in getNugetCommandName but documented nowhere. Now: restore, build, publish, pack, add, and nuget push, with an example and an explicit note.

This closes two open bug-hunt tickets about -h not showing supported args and jf dotnet nuget being undocumented.


2. dotnet FlexPack integration tests

Adds dotnet_native_test.go. That combination had no coverage — the empty quadrant:

File Toolchain Path
nuget_test.go nuget.exe + dotnet legacy
nuget_native_test.go nuget.exe FlexPack
dotnet_native_test.go dotnet FlexPack ← new

Derived from the Confluence test plan (RTFACT 2729476103). 114 tests against its 186 scenarios: all 40 P0, all 106 P1, 38/40 P2. Every test names the scenarios it covers, so coverage is auditable against the plan rather than asserted.

26 are t.Skip with a stated reason, in three groups:

  • Known product gaps — curation-on-failure hook not wired for dotnet; --scan accepted on push but stripped
  • Missing fixtures.fsproj / .vbproj / .slnx, signed packages, >100 MB packages
  • Infrastructure — promotion, Xray, release bundles, CI simulation, self-signed TLS, proxy. Each skip names the nuget_native_test.go helper to reuse when porting.

Infrastructure mirrors nuget_native_test.go and reuses its shared helpers rather than duplicating them. Runs under the existing -test.nuget flag, which is what provisions the NuGet repositories these tests share — there is no separate dotnet flag. nuget_native_test.go is untouched.


⚠️ Spec divergences, asserted as-implemented

Five P0 scenarios contradict the implementation. Following the precedent already set in nuget_native_test.go, the tests pin current behaviour, and the file header lists each divergence with both readings so a change in either direction shows up as a failure:

Scenario Plan says Code does
#145 / #156 no temp nuget.config is written one is written (sources only)
#150 JFrog creds used only for post-push stamping also used for resolve
#151 creds not exported to child env they are, via NuGetPackageSourceCredentials_*
#13 <repo>/<Name>/<Version>/<file>.nupkg lands flat at <repo>/<file>.nupkg

These need reconciling with the spec owner — either the spec or the implementation is wrong, and this PR doesn't decide which.

Testing

  • gofmt, go build ./..., go vet, golangci-lint — all clean
  • Test package compiles; verified end-to-end against a live Artifactory across 8 project types

Notes: gosec could not be run (internal error: package "embed" without types under this Go toolchain) — unverified rather than passing. The new tests are compile- and vet-verified but have not yet been run against a live Artifactory, so expect some to need adjustment on first CI run.

Merge order

Last of three RTECO-1782 PRs. Both dependencies must be merged and bumped first — TestDotnetFlexPackRequestedByHasNoRedundantPaths asserts the build-info-go dedupe behaviour.

  1. RTECO-1782: warn on externally-resolved deps and dedupe requestedBy paths build-info-go#422
  2. RTECO-1782: let the native client publish, and keep credentials off disk jfrog-cli-artifactory#551
  3. jfrog-cli ← this PR

🤖 Generated with Claude Code

bhanurp and others added 2 commits September 6, 2026 23:02
…help

The gate was `ShouldRunNative(configFilePath) && !configExists`, so any
.jfrog/projects/{dotnet,nuget}.yaml left in a project forced the legacy path
even with JFROG_RUN_NATIVE=true. Nothing was logged. Worse, the legacy path
does not recognise the native-only flags, so it forwarded them to MSBuild and
the run died with:

    MSBUILD : error MSB1001: Unknown switch.
    Switch: --repo-resolve

which names an MSBuild flag and gives no hint that a YAML file two directories
down is the cause. If the stale config happened to be valid, the build instead
resolved from whatever repository that file named rather than the
--repo-resolve on the command line - wrong-repo resolution with no diagnostic
at all.

The environment variable now takes precedence and the config file is reported
and ignored. Applied to both DotnetCmd and NugetCmd, which had the identical
gate and the identical failure. Verified that the legacy path still works when
JFROG_RUN_NATIVE is unset, and that a project with neither a config nor the
variable still gets the original "run jf dotnet-config first" error.

The help text was the other half of the problem. It listed 'jf dotnet-config'
as a prerequisite - "must be run first" - so a user following the CLI's own
documentation created exactly the file that disabled FlexPack. That command is
out of scope for FlexPack per the spec, yet remained the documented happy path.
It is now described as optional under JFROG_RUN_NATIVE, for both dotnet and
nuget.

Also corrected the dotnet sub-command list, which claimed "(restore, build,
pack, push)". There is no 'jf dotnet push'; the real command is the two-token
'jf dotnet nuget push', which was fully implemented in getNugetCommandName but
documented nowhere. The list now reads restore, build, publish, pack, add and
nuget push, with an example and an explicit note that plain 'jf dotnet push' is
not a command.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds dotnet_native_test.go, covering the dotnet CLI toolchain on the FlexPack
code path. That combination had no coverage: nuget_test.go covers both
toolchains on the legacy path, nuget_native_test.go covers nuget.exe on
FlexPack, and this fills the empty quadrant.

Derived from the Confluence test plan "Dotnet Flexpack support in jfrog-cli
test plan" (RTFACT 2729476103). 114 tests against its 186 scenarios: all 40 P0,
all 106 P1, 38 of 40 P2. Every test names the scenarios it covers, so coverage
is auditable against the plan rather than asserted.

26 tests are t.Skip with a stated reason, in three groups. Known product gaps:
the curation-on-failure hook is not wired for dotnet, and --scan is accepted on
push but stripped. Missing fixtures: .fsproj, .vbproj and .slnx projects,
signed packages, packages over 100 MB. Infrastructure this harness does not
provision: build promotion, Xray scan, release bundles, CI provider simulation,
self-signed TLS, and proxying - each skip names the nuget_native_test.go helper
to reuse when porting those groups.

Infrastructure mirrors nuget_native_test.go and reuses its shared helpers
(createNugetProject, getFlexPackItemProps, buildTestNupkg,
allowInsecureConnectionForFlexPackTests, createThrowawayRepo, initNugetTest,
cleanTestsHomeEnv) rather than duplicating them. Runs under the existing
-test.nuget flag, which is what provisions the NuGet repositories these tests
share; there is no separate dotnet flag.

Four P0 auth scenarios (jfrog#145, jfrog#150, jfrog#151, jfrog#156) and the published-path scenario
(jfrog#13) are asserted AS IMPLEMENTED rather than as specified, and the file header
lists each divergence with both readings. The plan states JFrog CLI injects no
temp nuget.config and exports nothing to the child environment; the
implementation does both, and packages land flat rather than under
<Name>/<Version>. nuget_native_test.go already carries the same divergence list
for nuget.exe. These need reconciling with the spec owner - the tests pin
current behaviour so that a change in either direction shows up as a failure.

Compile-verified and vet-clean; not yet run against a live Artifactory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI surfaced three classes of issue that could not be reproduced locally, because
the gosec build here fails with an internal type error under this Go toolchain.

G703 (path traversal, 11 sites): the .csproj rewrites are annotated with the
repo-standard justification. Every path is built from the test its own temp
project directory, never from external input, matching the existing precedent in
conan_test.go and nuget_native_test.go.

G122 and nilerr (one site): the sha512-corruption walk both wrote inside the
filepath.Walk callback, which gosec flags as a symlink TOCTOU race, and returned
nil when the walk itself errored. Sidecar paths are now collected during the walk
and rewritten after it returns, and the walk error is propagated. This is also
simply more correct: a failure to traverse the cache should fail the test rather
than silently corrupting nothing.

Verified with the exact commands CI runs: gosec reports 0 issues in both repos,
and golangci-lint with the Static Check linter set reports 0 issues.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bhanurp
bhanurp deployed to build-gate September 6, 2026 17:47 — with GitHub Actions Active
CI ran the suite for the first time and 65 of the failures traced to one line.
createNugetProject returns a path relative to the working directory, and
enterDotnetProject used it for two things that both require an absolute one:

NUGET_PACKAGES, which NuGet rejects outright -
  error : NUGET_PACKAGES must contain an absolute path out/reference/.packages
that accounted for 46 failures.

And file paths built by callers AFTER the helper chdirs into the project. A
relative projectPath then resolves against the project directory itself, so
writing out/reference/nuget.config from inside out/reference looked for
out/reference/out/reference/nuget.config - 19 more failures.

Resolving the path once at the top of the helper fixes both.

The remaining failure is a real product gap rather than a test defect, and is
now asserted as such. Credential injection appends --configfile to the end of
the argument list, which places it after a user double-dash separator;
everything past that separator goes to MSBuild, which rejects the switch:

    MSBUILD : error MSB1001: Unknown switch.
    Switch: --configfile

The injected flag needs to precede the separator. The subtest asserts the
current failure with that explanation, so it will start passing by itself once
the ordering is fixed rather than being silently skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The subtest documented the MSB1001 failure as a known gap. That gap is fixed in
jfrog-cli-artifactory (insertBeforeSeparator), so the case asserts success again
and the comment records why the ordering matters.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bhanurp
bhanurp deployed to build-gate September 7, 2026 02:15 — with GitHub Actions Active
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

👍 Frogbot scanned this pull request and did not find any new security issues.


docker_test.go: initDockerBuildTest set JFROG_RUN_NATIVE=true before calling
initNativeDockerWithArtTest, which t.Skip()s when '-test.docker=true' is absent.
t.Skip runs runtime.Goexit, so the helper never returned and the caller's
'defer cleanup()' was never registered - the variable stayed set for the rest of
the test binary. docker_test.go sorts before nuget_test.go, so every legacy
'jf nuget' test silently ran through the FlexPack path and TestNugetResolve's
requestedBy assertions failed. Run the skip check first and restore via
t.Cleanup so the value is undone even if a later helper skips or fails.

dotnet_native_test.go, TestDotnetFlexPackTransitiveDepsResolved: counted a
dependency as transitive when a RequestedBy path had len > 1. Since
stripModuleFromRequestedBy drops the trailing module ID, a one-level transitive
dep is ["bootstrap:4.0.0"] and a direct dep is ["reference:1.0.0"] - both
length 1 - and this fixture's graph is only one level deep, so the count was
always zero. Compare path[0] against the enclosing module ID instead.

dotnet_native_test.go, TestDotnetFlexPackPrivateAssetsScope: the test appended a
second PackageReference for Newtonsoft.Json carrying PrivateAssets="all", but
the fixture already declares that package. The SDK deduplicates duplicate
PackageReference items (NU1504) and keeps the first, so suppressParent never
reached project.assets.json and "compile" was correct. Mark the existing
reference private instead, guard the fixture literal, add the missing
ordinary-reference half of the scenario, and fail if neither dependency is
found rather than passing vacuously.
Explicit --source pushes (NugetApiKeyEnvVar, ApiKeyFlagOverridesEnv,
UserSourceOverridesConfig, CiSecretBackedApiKeyPush): NuGet 6.8+ refuses an HTTP
source unless allowInsecureConnections is set on a *configured* source, and jf
writes no nuget.config of its own when no --repo is given (NuGetFlexPackCommand
only injects one under `repo != ""`), so nothing supplied that permission and
the pushes died with "NuGet requires HTTPS sources" against the plain-HTTP test
Artifactory. Add insecureSourceConfigFile, which declares the same URL with
allowInsecureConnections and is passed via --configfile. It carries no
credential, so each test still proves what it was written to prove.

LocalRepoPublishAndResolve: resolution is pinned to a local repo holding only
the package just pushed, but the consumer project kept the simple-dotnet
fixture's four unrelated references, which that repo cannot serve - restore
failed NU1101 before the round trip was exercised. Replace the fixture's
ItemGroup instead of appending to it.

DependencyRangeResolvesConcreteVersion: was passing vacuously. It appended a
second PackageReference for a package the fixture already declares, so the SDK
collapsed the duplicate (NU1504) and the version range was discarded; the
assertions then held trivially. Turn the existing reference into a range, and
assert the range resolves to its lowest applicable version (13.0.0) rather than
merely that no dependency id contains a bracket.

LockedModeInconsistency: drifted the project by appending a duplicate reference,
which the SDK collapsed the same way - so the graph still matched the lock file,
locked mode restored happily and NU1004 never fired. Bump the existing
reference's version instead.

CentralPackageManagement: CPM is project-wide, so every PackageReference still
carrying a Version is NU1008 and fails the restore outright. Strip the versions
from all four fixture references and move them into Directory.Packages.props,
pinning Newtonsoft.Json centrally to a different version so the assertion proves
the version came from CPM.

BuildFlagsIncomplete: the CLI rejects a half-specified build-name/build-number
pair rather than silently skipping collection; assert the error instead of
NoError, and keep the check that no build-info is produced.

PushDefault: 'dotnet nuget push' publishes the .snupkg alongside the .nupkg, and
build-info types it "snupkg". Type each artifact by extension instead of
demanding "nupkg" for both, keeping the never-zip regression guard.

BceCapturesEnv and the two nuget_native_test.go 'bag' call sites: 'bce'/'bag'
are local commands, and the credential flags this runner appends land after the
positional args, where Go's flag parser has already stopped - so they were
counted as arguments ("Wrong number of arguments (4)"). The two nuget tests were
skipping over this while blaming a missing git repository. Use
WithoutCredentials(), as every other bce/bag call site in this suite does.

FlagPassthrough/double-dash-separator is left asserting the fixed behaviour; it
passes once go.mod resolves a jfrog-cli-artifactory carrying
insertBeforeSeparator (jfrog-cli-artifactory PR jfrog#551).
@bhanurp

bhanurp commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #3703, which is the same work on a branch in this repository rather than a fork. A PR's head branch cannot be changed after creation, so reopening was the only way to move it.

@bhanurp bhanurp closed this Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant