Skip to content

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

Open
bhanurp wants to merge 7 commits into
masterfrom
RTECO-2247
Open

bhanurp wants to merge 7 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.

Depends on (must merge first, or this repo's go.mod needs re-pinning to released versions once they land):

Note on go.mod: this branch carries replace directives pinning all three dependencies above to their own RTECO-2247(-psresource) branches (same canonical repos, not a fork) so this repo can build against the unreleased changes. Marked with an explicit release-blocking comment in go.mod — these need to come out once the sibling PRs merge and real released versions can be pinned.

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


Marked as draft: this repo's required No-Replace check hard-fails while any replace github.com/jfrog/* directive is active in go.mod (regardless of target) — and this branch needs one for each of build-info-go#430, jfrog-cli-core#1620, and jfrog-cli-artifactory#563 until they merge and this branch can pin real released versions. Will mark ready for review once those merge and the replace directives come out.

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>
@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.")
}



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

This branch is waiting to be deployed

1 waiting deployment
build-gate 96c4148a Waiting Sep 22, 2026 by bhanurp via Approval gate #1221
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.

2 participants