Skip to content

RTECO-2247: Add jf Install-/Save-/Update-/Publish-PSResource commands - #3717

Merged
itsmeleela merged 11 commits into
masterfrom
RTECO-2247
Sep 28, 2026
Merged

itsmeleela merged 11 commits into
masterfrom
RTECO-2247

Conversation

@bhanurp

@bhanurp bhanurp commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Part of RTECO-2247 — registers four top-level commands wrapping PowerShell's PSResourceGet module: jf Install-PSResource, jf Save-PSResource, jf Update-PSResource, jf Publish-PSResource. Deliberately four separate commands (matching native PowerShell cmdlet names) rather than one jf psresource <verb> wrapper — unlike jf choco, which uses a single-command, first-positional-argument dispatch.

Dependencies — all three merged, and go.mod now pins each one at its merge commit:

Note on go.mod: the three release-blocking replace directives are gone — each dependency is pinned at the merge commit of its sibling PR, so the required No-Replace check passes. Each of those commits is the current head of its default branch and already contains the Chocolatey work, so nothing is held back. The transitive golang.org/x/* bumps (including crypto v0.57.0) come with those pins.

What's here

  • buildtools/cli.go — the four cli.Command entries, built from a shared table (psResourceCommandEntries) rather than four hand-duplicated literals, all backed by one psResourceCmd(cmdletName) helper.
  • docs/buildtools/psresource/help.go — per-cmdlet help text, rendered from one shared template parameterized by a small cmdletMeta struct (the four cmdlets' help is ~90% identical prose).
  • utils/cliutils/commandsflags.go — the shared flag set (--build-name, --build-number, --module, --project, --repo, --repo-resolve, --server-id).
  • Integration tests (psresource_test.go) — help text on every platform, CLI-level build-flag-pair validation, and (skipping gracefully without a local pwsh + PSResourceGet install) end-to-end dependency/artifact build-info collection for all four commands.
  • CI workflow (psresourceTests.yml) — cross-platform (Linux/macOS/Windows), unlike jf choco's Windows-only requirement, since PSResourceGet via pwsh is cross-platform.

Notable bug fix included (found via an adversarial multi-agent code review after the initial implementation)

  • Curation audit silently disabled: WrapCmdWithCurationPostFailureRun was called with each cmdlet's own PascalCase name ("Install-PSResource", etc.) as cmdName, but jfrog-cli-security's post-failure curation audit gates on a fixed, generic verb allowlist ({install, build, i, add, ci, get, mod}) shared across every package manager — none of our names was ever in it, so the audit was a silent no-op for all four commands. Install/Save/Update now pass the matching "install" verb; Publish (which uploads rather than resolves a package, so curation can't block it the way this audit checks for) runs directly without a curation cmdName that would never apply.

Test plan

  • go test . -run PSResource -v -args -test.psresource=true and go test ./buildtools/... ./docs/buildtools/psresource/... — all pass (end-to-end tests skip gracefully on this machine, which has no pwsh installed).

  • New regression tests for the curation-cmdName fix, GetAIDescription's per-cmdlet map lookup, and dispatch-reaching coverage for Save/Update/Publish (previously only Install-PSResource's real dispatch logic was ever exercised past the shared --help early-return).

  • golangci-lint clean, gofmt clean.

  • All tests passed. New tests added for the bug found during review.

  • All static analysis checks passed.

  • This pull request is on the master branch.

  • I used gofmt for formatting the code before submitting the pull request.

  • Full flow was locally tested (help text on every platform; end-to-end paths verified with pwsh unavailable, which is the graceful-skip path).

🤖 Generated with Claude Code


Ready for review: the sibling PRs have merged and the replace directives have been removed, so the required No-Replace check no longer blocks this branch. upstream/master has also been merged in, resolving the overlap with the Chocolatey PR (#3708) in buildtools/cli.go, docs/buildtools/setup/help.go, utils/tests/consts.go and utils/tests/utils.go by keeping both package managers.

bhanurp and others added 4 commits September 18, 2026 16:15
…p-level jf commands

Wire up the four native PowerShell PSResourceGet cmdlets as separate top-level
jf commands (deliberately not a single `jf psresource <verb>` wrapper), mirroring
the shipped `jf choco` FlexPack pattern from the unmerged RTECO-2003 branch:

- Pin jfrog-cli-artifactory, jfrog-cli-core and build-info-go to the bhanurp
  fork's RTECO-2247 branch (go.mod replace directives) to pick up
  PSResourceFlexPackCommand, project.PSResource and the PSResource collectors.
- buildtools/cli.go: add Install-PSResource, Save-PSResource, Update-PSResource
  and Publish-PSResource commands, backed by a shared psResourceCmd(cmdlet)
  helper, plus a jf setup psresource platform gate (ValidatePSResourcePlatform).
- docs/buildtools/psresource: per-cmdlet usage/description/AI-description text.
- utils/cliutils/commandsflags.go: PSResource flag set (mirrors Choco's).
- docs/buildtools/setup/help.go: mention psresource's prerequisites/gotchas.
- utils/tests: test.psresource flag and repo/build-name scaffolding.
- psresource_test.go: integration tests that skip gracefully when pwsh +
  Microsoft.PowerShell.PSResourceGet aren't available, since PSResourceGet is
  cross-platform (unlike choco, which is Windows-only).
- .github/workflows/psresourceTests.yml: cross-platform (ubuntu/macos/windows)
  CI job, wired into build-gate.yml.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace the four near-identical Install-/Save-/Update-/Publish-PSResource
cli.Command literals in buildtools/cli.go with a table-driven
psResourceCommandEntries() loop over the four native cmdlet names, and
replace the 16 hand-duplicated per-verb functions/vars in
docs/buildtools/psresource/help.go with a single templated
Usage/GetDescription/GetArguments/GetAIDescription set parameterized by
cmdlet name and a small per-cmdlet cmdletMeta map for the AI description's
varying prose. Also avoids computing ResolveDescription twice per
PSResource command (reused for both Usage and HelpName).

This is a pure cleanup/dedup refactor - no behavior change. Verified all
four commands' --help output (Name/Usage/Arguments/Options sections) is
byte-identical before and after via a temporary before/after snapshot
against the pre-refactor files, plus go build/vet, golangci-lint, and the
PSResource-scoped tests in psresource_test.go all pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…review

- buildtools/cli.go: WrapCmdWithCurationPostFailureRun was called with
  each cmdlet's own PascalCase name ("Install-PSResource" etc.) as
  cmdName, but jfrog-cli-security's post-failure curation audit gates
  on a fixed, generic verb allowlist ({install, build, i, add, ci,
  get, mod}) shared across every package manager - none of our names
  was ever in it, so the audit was a silent no-op for all four
  commands. Install/Save/Update now pass the matching "install" verb;
  Publish-PSResource (which uploads rather than resolves a package,
  so curation cannot block it the way this audit checks for) now
  runs directly, without a curation cmdName that would never apply.
- Re-pin the three in-flight fork dependencies (jfrog-cli-artifactory,
  jfrog-cli-core, build-info-go) to their latest commits, and add an
  explicit release-blocking comment in go.mod: these replace
  directives point at a personal fork and must be removed once the
  corresponding upstream PRs land - not something to "fix" by ripping
  them out now, since this repo cannot build the in-flight PSResource
  support without them yet.
- docs/buildtools/psresource/help_test.go: GetAIDescription's
  per-cmdlet map lookup had zero test coverage across any of the four
  cmdlets.
- psresource_test.go: the install build-info test made no assertion
  about build-info actually being collected on success, and only
  Install-PSResource's real dispatch logic (past the shared --help
  early-return) was ever exercised by any test. Added the same
  ValidateGeneratedBuildInfoModule assertion the equivalent NuGet test
  uses, and added matching tests for Save-/Update-/Publish-PSResource.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
All three in-flight dependencies (jfrog-cli-artifactory, jfrog-cli-core,
build-info-go) are now pushed directly to their jfrog org repos
(RTECO-2247, or RTECO-2247-psresource for jfrog-cli-artifactory, which
has a branch-naming rule requiring a suffix), so these replace
directives no longer need to point at the bhanurp personal fork.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@bhanurp
bhanurp marked this pull request as draft September 21, 2026 09:20
@bhanurp bhanurp added the new feature Automatically generated release notes label Sep 21, 2026
shell := "" was always overwritten by one of the two LookPath branches
before ever being read, tripping wastedassign in CI's Static Check.
Uses exec.LookPath's own error return to choose the fallback instead
of a pre-initialized variable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread .github/workflows/psresourceTests.yml Outdated
Address PR review feedback: GitHub-hosted runners (ubuntu-latest,
macos-latest, windows-latest) already ship PowerShell 7 as pwsh out
of the box, so installing it via apt/curl/brew is unnecessary. Replace
the install step with a simple verification step (pwsh -v) as
suggested by the reviewer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@bhanurp
bhanurp marked this pull request as ready for review September 22, 2026 11:03
Resolves the choco/psresource overlap (upstream #3708) in buildtools/cli.go,
docs/buildtools/setup/help.go, utils/tests/consts.go and utils/tests/utils.go by
keeping both package managers, and re-pins build-info-go, jfrog-cli-core and
jfrog-cli-artifactory to their merged PSResource commits so the three
RELEASE-BLOCKING replace directives could be removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…dows

Re-pins jfrog-cli-artifactory to 8c77089 (#569, "align Chocolatey source
handling with nuget, dotnet and psresource"). That commit touches only the choco
and dotnet packages, so it carries no psresource source change.

Scopes both native-shell suites to a single Windows job:

- psresourceTests.yml drops the ubuntu/windows/macos matrix. PSResourceGet is
  cross-platform, so this is a coverage choice rather than a constraint, and the
  comment now says so instead of arguing for the matrix.
- chocoTests.yml drops the linux "Chocolatey OS gate" job. That job installs no
  Artifactory by design, but the tests it runs still reach
  localhost:8081/artifactory/api/repositories during setup, so it fails with
  "connection refused" on master today.

Also adds the allow-unsafe-pr-checkout flag the other suites pass, since
build-gate invokes this workflow from a pull_request_target context.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TestSetupPSResourceConfiguresRepository failed on the Windows runner with
"The repository 'cli-nuget-virtual' does not exist", because --test.psresource
was missing from the two build-tools gates in TestMain/tearDownIntegrationTests.

Without it, InitBuildToolsTests never ran for the suite, so no NuGet
local/remote/virtual repositories were created and the tests/*.go globals kept
their unsuffixed defaults - the tests asked Artifactory for 'cli-nuget-virtual'
while every other suite works against 'cli-nuget-virtual-<timestamp>'.
CleanBuildToolsTests was skipped for the same reason, leaking repositories.

Adding *tests.TestPSResource next to *tests.TestChoco in both conditions is all
that is needed; the repository, virtual-repository and build-name maps in
utils/tests/utils.go were already wired for the suite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TestChocoCommandPropertyRedactsApiKey: chocoPushApiKey built the '-k' value as
serverDetails.User + ":" + <secret>, and authenticate() sets either User+Password
or AccessToken, never both. Against a local Artifactory the suite always gets an
access token, so User was empty and the pushed pair was ":<secret>". Chocolatey
rejects that with "Invalid credentials specified", falls back to prompting for a
username on stdin, and aborts with System.InvalidOperationException on a runner
with no console. Recover the username from the access token's own JWT subject via
auth.ExtractUsernameFromAccessToken, the way jf's own dotnetcommand.go does,
falling back to the configured test user for reference tokens and API keys, which
carry no subject.

TestChocoInstallCollectsDependencies: the dependency's 'repository' was asserted
against the build-info fetched back from Artifactory, where it is always empty -
Artifactory's build-info schema has no per-dependency repository field, so the
value does not survive a publish/fetch round trip. The collector does set it
(covered in build-info-go by flexpack/choco's own unit tests), and the CLI does
forward --repo-resolve, so assert it against the locally collected build-info
instead, which is the part of the contract jfrog-cli actually owns. The read has
to happen before the publish, because publishing clears the local build state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚨 Frogbot scanned this pull request and found the below:

View full scan results in JFrog Platform

📗 Scan Summary

  • Frogbot scanned for vulnerabilities and found 3 issues
Scan Category Status Security Issues
Software Composition Analysis ✅ Done Not Found
Contextual Analysis ✅ Done -
Static Application Security Testing (SAST) ✅ Done
3 Issues Found 3 Low
Secrets ✅ Done Not Found
Services ✅ Done Not Found
Infrastructure as Code (IaC) ✅ Done Not Found

@github-actions

Copy link
Copy Markdown
Contributor
NpmLocalRepositoryConfig

at utils/tests/consts.go (line 96)

🎯 Static Application Security Testing (SAST) Vulnerability

Severity Finding
low
Low
Credentials for restricted resources included directly in source code
Full description

Vulnerability Details

Rule ID: go-hardcoded-credentials

Overview

Hardcoded credentials are usernames, passwords, API keys, or other secrets
embedded directly in source code. This practice, identified by CWE-798, is
highly insecure because it makes it easy for anyone with access to the code to
discover and misuse the credentials. If the code is publicly released, shared,
or leaked, the credentials will be exposed to unauthorized parties.

Vulnerable example

In this example, the database username and password for the frog pond are
hardcoded directly in the source code as string literals. This is a major
security risk, as anyone who can read this file can steal the credentials and
gain unauthorized access to the database.

package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)

func main() {
	// VULNERABLE: Hardcoded database credentials for the frog pond.
	frogUser := "pond_admin"
	frogPassword := "LeapFlog123!"
	pondName := "lilypad_db"

	connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
		frogUser, frogPassword, pondName)

	lilypadDB, err := sql.Open("mysql", connStr)
	if err != nil {
		log.Fatalf("Error opening database: %v", err)
	}
	defer lilypadDB.Close()

	err = lilypadDB.Ping()
	if err != nil {
		log.Fatalf("Error pinging database: %v", err)
	}
	fmt.Println("Successfully connected to the frog pond.")
}

Remediation

The remediated code retrieves the database credentials from environment
variables instead of hardcoding them. This is a much more secure approach, as
it separates the secrets from the source code. This allows credentials to be
managed securely by deployment systems and rotated without changing the code.

package main

import (
	"database/sql"
	"fmt"
	"log"
	"os"

	_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)

func main() {
	// SECURE: Retrieve credentials from environment variables.
	frogUser := os.Getenv("FROG_DB_USER")
	frogPassword := os.Getenv("FROG_DB_PASS")
	pondName := os.Getenv("FROG_DB_NAME")

	if frogUser == "" || frogPassword == "" || pondName == "" {
		log.Fatal("DB credentials are not set in environment variables.")
	}

	connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
		frogUser, frogPassword, pondName)

	lilypadDB, err := sql.Open("mysql", connStr)
	if err != nil {
		log.Fatalf("Error opening database: %v", err)
	}
	defer lilypadDB.Close()

	err = lilypadDB.Ping()
	if err != nil {
		log.Fatalf("Error pinging database: %v", err)
	}
	fmt.Println("Successfully connected to the frog pond.")
}



@github-actions

Copy link
Copy Markdown
Contributor
NpmLocalScopedRespositoryConfig

at utils/tests/consts.go (line 97)

🎯 Static Application Security Testing (SAST) Vulnerability

Severity Finding
low
Low
Credentials for restricted resources included directly in source code
Full description

Vulnerability Details

Rule ID: go-hardcoded-credentials

Overview

Hardcoded credentials are usernames, passwords, API keys, or other secrets
embedded directly in source code. This practice, identified by CWE-798, is
highly insecure because it makes it easy for anyone with access to the code to
discover and misuse the credentials. If the code is publicly released, shared,
or leaked, the credentials will be exposed to unauthorized parties.

Vulnerable example

In this example, the database username and password for the frog pond are
hardcoded directly in the source code as string literals. This is a major
security risk, as anyone who can read this file can steal the credentials and
gain unauthorized access to the database.

package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)

func main() {
	// VULNERABLE: Hardcoded database credentials for the frog pond.
	frogUser := "pond_admin"
	frogPassword := "LeapFlog123!"
	pondName := "lilypad_db"

	connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
		frogUser, frogPassword, pondName)

	lilypadDB, err := sql.Open("mysql", connStr)
	if err != nil {
		log.Fatalf("Error opening database: %v", err)
	}
	defer lilypadDB.Close()

	err = lilypadDB.Ping()
	if err != nil {
		log.Fatalf("Error pinging database: %v", err)
	}
	fmt.Println("Successfully connected to the frog pond.")
}

Remediation

The remediated code retrieves the database credentials from environment
variables instead of hardcoding them. This is a much more secure approach, as
it separates the secrets from the source code. This allows credentials to be
managed securely by deployment systems and rotated without changing the code.

package main

import (
	"database/sql"
	"fmt"
	"log"
	"os"

	_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)

func main() {
	// SECURE: Retrieve credentials from environment variables.
	frogUser := os.Getenv("FROG_DB_USER")
	frogPassword := os.Getenv("FROG_DB_PASS")
	pondName := os.Getenv("FROG_DB_NAME")

	if frogUser == "" || frogPassword == "" || pondName == "" {
		log.Fatal("DB credentials are not set in environment variables.")
	}

	connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
		frogUser, frogPassword, pondName)

	lilypadDB, err := sql.Open("mysql", connStr)
	if err != nil {
		log.Fatalf("Error opening database: %v", err)
	}
	defer lilypadDB.Close()

	err = lilypadDB.Ping()
	if err != nil {
		log.Fatalf("Error pinging database: %v", err)
	}
	fmt.Println("Successfully connected to the frog pond.")
}



@github-actions

Copy link
Copy Markdown
Contributor
NpmRemoteRepositoryConfig

at utils/tests/consts.go (line 98)

🎯 Static Application Security Testing (SAST) Vulnerability

Severity Finding
low
Low
Credentials for restricted resources included directly in source code
Full description

Vulnerability Details

Rule ID: go-hardcoded-credentials

Overview

Hardcoded credentials are usernames, passwords, API keys, or other secrets
embedded directly in source code. This practice, identified by CWE-798, is
highly insecure because it makes it easy for anyone with access to the code to
discover and misuse the credentials. If the code is publicly released, shared,
or leaked, the credentials will be exposed to unauthorized parties.

Vulnerable example

In this example, the database username and password for the frog pond are
hardcoded directly in the source code as string literals. This is a major
security risk, as anyone who can read this file can steal the credentials and
gain unauthorized access to the database.

package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)

func main() {
	// VULNERABLE: Hardcoded database credentials for the frog pond.
	frogUser := "pond_admin"
	frogPassword := "LeapFlog123!"
	pondName := "lilypad_db"

	connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
		frogUser, frogPassword, pondName)

	lilypadDB, err := sql.Open("mysql", connStr)
	if err != nil {
		log.Fatalf("Error opening database: %v", err)
	}
	defer lilypadDB.Close()

	err = lilypadDB.Ping()
	if err != nil {
		log.Fatalf("Error pinging database: %v", err)
	}
	fmt.Println("Successfully connected to the frog pond.")
}

Remediation

The remediated code retrieves the database credentials from environment
variables instead of hardcoding them. This is a much more secure approach, as
it separates the secrets from the source code. This allows credentials to be
managed securely by deployment systems and rotated without changing the code.

package main

import (
	"database/sql"
	"fmt"
	"log"
	"os"

	_ "[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)"
)

func main() {
	// SECURE: Retrieve credentials from environment variables.
	frogUser := os.Getenv("FROG_DB_USER")
	frogPassword := os.Getenv("FROG_DB_PASS")
	pondName := os.Getenv("FROG_DB_NAME")

	if frogUser == "" || frogPassword == "" || pondName == "" {
		log.Fatal("DB credentials are not set in environment variables.")
	}

	connStr := fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/%s",
		frogUser, frogPassword, pondName)

	lilypadDB, err := sql.Open("mysql", connStr)
	if err != nil {
		log.Fatalf("Error opening database: %v", err)
	}
	defer lilypadDB.Close()

	err = lilypadDB.Ping()
	if err != nil {
		log.Fatalf("Error pinging database: %v", err)
	}
	fmt.Println("Successfully connected to the frog pond.")
}



@itsmeleela
itsmeleela merged commit ae3ec77 into master Sep 28, 2026
920 of 945 checks passed
@itsmeleela
itsmeleela deleted the RTECO-2247 branch September 28, 2026 07:04

This branch was successfully deployed

1 active deployment
build-gate — eda161e1 Deployed Sep 27, 2026 by bhanurp via Approval gate #1249
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new feature Automatically generated release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants