Skip to content

RTECO-1574 - Implementation of Nuget Support for client - - #3664

Open
bhanurp wants to merge 30 commits into
masterfrom
RTECO-1574
Open

RTECO-1574 - Implementation of Nuget Support for client -#3664
bhanurp wants to merge 30 commits into
masterfrom
RTECO-1574

Conversation

@bhanurp

@bhanurp bhanurp commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Generated by workbench RTECO-1574.

RTECO-1574 - Implementation of Nuget Support for client -

  • Start working on Nuget V2, V3 support for Nuget package manager in jfrog cli.
  • Make sure PRs are manageable and code is written in a way it can be reused across the clients
  • Add support for nugetV3 and nugetV2
  • refer to [unsupported block: inlineCard]

Considerations while implementation

  • Use JFROG_CLI_NATIVE_IMPLEMENTATION support since nuget is already supported when this is set it routes via native package manager(flexpack).
  • BuildInfo collection in build-info-go
  • check sum calculation
  • Original Deployment Repository calculation
  • Requested By calculation for dependencies
  • Easy authentication using nuget's credentials like mentioned in above Atlassian wiki.
  • Build Info collection for both dependencies and artifacts for all the commands eligible listed in above wiki.
  • Set properties clearly on artifacts published.
  • Make sure the buildinfo is figuring out and showing show in tree in build info section when published to artifactory.

Not to implement

  • nuget-config support is not required since this is wrapper on native binary.
    Parent: RTECO-395
    Components: jfrog-cli-nuget

Evidence: see Artifactory bundle for RTECO-1574.

Jira: https://jfrog-int.atlassian.net/browse/RTECO-1574


Supersedes #3616, opened from a branch pushed directly to this repo (rather than a fork) so that pull_request_target-gated workflows (e.g. apt tests) resolve their workflow YAML from the correct ref.

bhanurp and others added 30 commits July 24, 2026 09:29
**Source:** https://jfrog-int.atlassian.net/browse/RTECO-1574
- Start working on Nuget V2, V3 support for Nuget package manager in jfrog cli.
- Make sure PRs are manageable and code is written in a way it can be reused across the clients
- Add support for nugetV3 and nugetV2
- refer to [unsupported block: inlineCard]
## Things to consider while implementing
- Use JFROG_CLI_NATIVE_IMPLEMENTATION support since nuget is already supported when this is set it routes via native package manager(flexpack).
- BuildInfo collection in build-info-go
- check sum calculation
- Original Deployment Repository calculation
- Requested By calculation for dependencies
- Easy authentication using nuget’s credentials like mentioned in above Atlassian wiki.
- Build Info collection for both dependencies and artifacts for all the commands eligible listed in above wiki.
- Set properties clearly on artifacts published.
- Make sure the buildinfo is figuring out and showing show in tree in build info section when published to artifactory.
## Not to implement
- nuget-config support is not required since this is flexpack implementation.
**Parent:** RTECO-395
**Components:** jfrog-cli-nuget

Task: RTECO-1574
- Only resolve/require server details for FlexPack NuGet commands that
  actually need them (push with a deploy repo, or restore with a resolve
  repo), instead of unconditionally requiring a --server-id for every
  subcommand.
- Renamed --repo-deploy to --repo and --allow-insecure-connections to
  --insecure-tls to match the flags actually documented/used elsewhere.
- Added getNugetCommandName to correctly treat dotnet's two-token
  "nuget push" as a single push subcommand, instead of misclassifying it.
- Replaced the temporary local-filesystem-path go.mod replaces for
  build-info-go and jfrog-cli-artifactory (committed by mistake in the
  previous commit on this branch) with proper pseudo-version pins against
  their latest pushed RTECO-1574 commits, which fix: packages.config
  checksum/scope/cache-miss gaps, the flat NuGet push storage path
  assumption that broke property stamping, a restore-vs-push module
  identity mismatch that split one project into two disconnected build-info
  modules, missing .slnx solution support, and a missing --source flag
  that made every native push fail outright.

Verified live against a real Artifactory server, building purely from the
pinned remote commits (no local path replaces).
- nuget_native_test.go: full scenario-table coverage for the FlexPack
  native `jf nuget` client (JFROG_RUN_NATIVE=true), separate from and
  not touching the existing legacy nuget_test.go suite.
- buildtools/cli.go: --scan wasn't extracted before invoking nuget.exe
  in the FlexPack path, so it leaked through and nuget.exe rejected it
  with "Unknown option: '--scan'". Strip it like the other jf-level
  flags; conditional-upload Xray scanning itself isn't wired for NuGet
  FlexPack yet (same documented gap as the curation hook).
- utils/tests/consts.go, utils/tests/utils.go: add NugetLocalRepo/
  NugetVirtualRepo fixtures alongside the existing NugetRemoteRepo one,
  needed by the new test suite's local-repo and virtual-repo scenarios.
- testdata/nuget_{local,virtual}_repository_config.json: corresponding
  repo-config templates.

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

Replaces local filesystem paths (used for live local testing) with
github.com/bhanurp/build-info-go@74d0864 and
github.com/bhanurp/jfrog-cli-artifactory@55bd344, so CI can resolve
these dependencies.

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

The Aug 7 merge of master into RTECO-1574 botched the import block in
buildtools/cli.go: it duplicated the nixcommand import and dropped the
nugetcommand import entirely, replacing it with an unrelated
same-named-concept package (build-info-go's dotnetutils, which is
still needed separately for the ToolchainType enum). Restored the
correct import (jfrog-cli-artifactory/artifactory/commands/nuget).

Also bumped both replace directives to the latest pushed fork commits:
build-info-go@978cd54 (includes reconciling a duplicate .slnx
implementation that the same master merge introduced upstream-side)
and jfrog-cli-artifactory@63a5fd2 (now caught up with its own
upstream/main, including APT auth command support jfrog-cli's merged
master code now depends on).

Verified: go build -tags nuget ./... and go vet -tags nuget . both
clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- gosec G703 (path traversal via taint analysis) x5: all flagged
  os.ReadFile/os.WriteFile calls operate on paths derived from this
  test's own t.TempDir()/testdata fixtures, never untrusted input.
  Annotated with #nosec and a justification each.
- errcheck: unchecked res.Body.Close() error in doAccessRequest.
- staticcheck SA4010: the 'paths' slice in
  TestNugetFlexPackPushWildcardGlob was appended to but never read
  (the test pushes via a glob pattern directly) - removed the dead
  variable entirely rather than working around the warning.

Verified: golangci-lint run with the same flag set as the Go-Sec CI
job is clean across the whole package.

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

jfrog-cli's own go.mod had a stale direct replace (74d0864, pre-.slnx
reconciliation) even though jfrog-cli-artifactory's go.mod already
pointed at 978cd54 - Go only honors replace directives from the root
module, not from dependencies, so this one actually governed the
build. Aligned to the same commit for consistency with the upstream
build-info-go PR.

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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every 'jf nuget'/'jf dotnet' command now runs through the FlexPack
build-info path (runNugetFlexPackCmd), which reports a dependency's
RequestedBy as only the chain of packages that pulled it in, not the
enclosing project/module it's already grouped under. Direct
dependencies now have empty RequestedBy instead of [moduleName].
…asserters

RequestedBy no longer echoes the enclosing module, so the assertion
helpers no longer need the module name.
NugetCmd/DotnetCmd called ShouldRunNative("") with a hardcoded empty
path, so whenever FlexPack was enabled (JFROG_RUN_NATIVE=true) every
invocation went native even when a legacy nuget-config/dotnet-config
file existed - silently changing output for existing config-based
setups. Follow the same pattern already used by Maven/Gradle: resolve
the real config path first and only go native when no config exists.

Revert the TestNugetResolve RequestedBy assertion changes from the
prior two commits - those tests create a legacy config file, so with
this fix they exercise the legacy path again and keep their original
expected shape.
The legacy path only stripped --allow-insecure-connections, so
--insecure-tls (the flag name tests and newer callers use, expecting
FlexPack to strip it) leaked through to nuget.exe/dotnet, which reject
it as an unknown option. This broke every legacy-config-based restore
identically on Linux and Windows once the previous commit correctly
routed config-based invocations back to the legacy path.
…tests

Every call site in nuget_test.go creates a legacy nuget-config/dotnet-config
file, routing through the legacy NugetCmd/DotnetCmd path, which only
recognizes --allow-insecure-connections. --insecure-tls is FlexPack's own
flag name, stripped only on that path - it should not be handled by legacy
at all. Fixing the test's flag name instead of teaching legacy about a flag
it does not need keeps the two paths cleanly separated: FlexPack strips its
flag, legacy passes its own through unmodified.
nuget_native_test.go's ~18 call sites shared nuget_test.go's
allowInsecureConnectionForTests helper, which the previous commit
changed to append --allow-insecure-connections for the legacy path.
Every FlexPack-only test in this file has no config file and always
routes through runNugetFlexPackCmd, which only strips --insecure-tls -
so that change broke every one of them with the same "Unknown option"
failure we just fixed on the other side.

Added a separate allowInsecureConnectionForFlexPackTests helper scoped
to this file instead of re-introducing flag aliasing into either CLI
path.
TestNugetFlexPackBuildScanReportsVulnerabilities and
TestNugetFlexPackBuildScanAfterPromotion call 'jf rt build-scan'
unconditionally, but the CI nuget job's local JFrog Platform container
never starts Xray (its startup log only shows Artifactory, router,
metadata, frontend, observability, onemodel, topology, event,
jfconnect, jfbus, jfmelt - no xray). This file already has the right
pattern for this (see TestNugetFlexPackFullStatelessPipeline's
'if *tests.TestXray' guard around its own build-scan call); the two
scan-only tests were just missing it entirely.

TestNugetFlexPackBuildScanFullTransitiveTree is unaffected - it never
calls build-scan itself, only reads published build-info.
createThrowawayProject sent a hardcoded "Authorization: Bearer
<serverDetails.AccessToken>" header, but the CI nuget job configures
its local server with a username/password, not a token - so
AccessToken is empty there and the Access API correctly 403s an empty
bearer token. Switched to artUtils.CreateAccessServiceManager, the
same SDK-backed mechanism artifactory_test.go's own project-scoped
tests already use, which works with whichever auth serverDetails
actually holds. Removed the now-unused accessApiBaseUrl helper.

TestNugetFlexPackReleaseBundleFromNugetBuild called 'jfrog rbc'
unconditionally and 403'd for the same reason TestXray-gated tests
would without Xray: the CI job's local platform does not have
Lifecycle/Release Bundles v2 enabled. Gated it behind the existing
*tests.TestLifecycle flag.
nugetTests.yml only passed --jfrog.url/--jfrog.adminToken when
JFROG_TESTS_IS_EXTERNAL=='true'; for the normal local-install case it
passed neither, so the test binary fell back to its jfrog.url flag's
hardcoded default (http://localhost:8081/) - Artifactory's own direct
port. install-local-artifactory's local-rt-setup step actually exports
JFROG_TESTS_URL=http://127.0.0.1:8082, the platform router that fronts
Lifecycle/onemodel (Artifactory's own port has no /lifecycle route,
which is why 'jfrog rbc' came back as a raw Tomcat 403 HTML page, not
a JSON API error). lifecycleTests.yml already passes these flags
unconditionally - mirrored that same pattern here.

With the router now reachable, TestNugetFlexPackReleaseBundleFromNugetBuild
no longer needs the *tests.TestLifecycle gate added two commits ago -
it is fully self-contained (creates/cleans up its own build and
release bundle) and does not depend on the job-wide --test.lifecycle
flag's much larger provisioning step.

Xray remains genuinely unavailable in this setup regardless of URL -
no workflow in this repo passes --test.xray or provisions an Xray
service via local-rt-setup, so the two build-scan tests keep their
*tests.TestXray skip gate from the earlier commit.
Revert the nugetTests.yml change from the previous commit - pointing
the whole job at the router port risked changing behavior for all
~85 other passing tests, unverified against the actual CI setup.

Instead, added withLifecycleRouterUrl, which temporarily repoints just
the "default" server profile at port 8082 (rewriting only ":8081", a
no-op against external servers like ecosys with no such port split)
for the single 'jfrog rbc' call in
TestNugetFlexPackReleaseBundleFromNugetBuild, then restores the
original URL immediately after. Every other test in this file keeps
using Artifactory's direct port exactly as before.
…skip the ad-hoc -Source TLS test

createThrowawayProject's AccessServicesManager fix (previous commit)
resolved the auth mechanism, but still 403'd identically to 'jfrog
rbc' on CI - same raw Tomcat 403 HTML page, because it built the
manager from the plain serverDetails, which points at Artifactory's
direct port. The Access API is fronted by the platform router just
like Lifecycle, not exposed on Artifactory's own port.

Factored the ":8081"->":8082" rewrite (already used by
withLifecycleRouterUrl) into a shared platformRouterUrl helper, and
added routerServerDetails to build a router-pointed *config.ServerDetails
copy for SDK-level calls that take one directly instead of going
through the CLI's own config profile.

TestNugetFlexPackTlsSelfSignedRequiresInsecureFlag: skipped per
diagnosis - it pushes through an explicit -Source override, which
nuget.exe treats as an ad-hoc source bypassing the generated
nuget.config's allowInsecureConnections entirely. Confirmed failing
identically on macOS, Linux, and Windows, so this is not something
--insecure-tls's wiring can fix; the test's own premise doesn't hold
against how nuget.exe actually resolves an inline -Source.
…oncreteVersion

The test patches reference.csproj's bootstrap PackageReference version
by matching a literal string containing a bare "\n" between the
<PackageReference> and <Version> lines. On the Windows CI runner, git
checks this fixture out with CRLF line endings, so the literal match
never fired - strings.Replace silently returned the original content
unchanged, and the very next require.NotEqual caught it and failed the
test in setup, before any restore/push ran. Ubuntu and macOS checkout
this file with plain LF, which is why only Windows failed.

Normalize CRLF to LF before matching/patching. The rewritten content
is written back as LF-only, which MSBuild/nuget.exe parse identically
to CRLF - XML doesn't care about line-ending style.
# Conflicts:
#	buildtools/cli.go
#	go.mod
#	go.sum
aptTests.yml is invoked the same way nugetTests.yml/lifecycleTests.yml
are - a workflow_call-only reusable workflow gated behind
build-gate.yml's pull_request_target + human approval - but its
Checkout code step was missing allow-unsafe-pr-checkout: true, which
the other two already have. Without it, checkout of the PR head SHA
under pull_request_target's elevated privileges is blocked, which is
why the apt job's tests have been failing.

Checked the rest of .github/workflows for the same gap: analysis.yml's
checkout steps don't need it (triggered by plain pull_request, not
pull_request_target, so the elevated-privilege risk this flag guards
against doesn't apply), and dependabot-auto-merge.yml doesn't check
out PR code at all.
@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
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.")
}



Comment thread utils/tests/consts.go
@@ -97,6 +97,8 @@ const (
NpmLocalScopedRespositoryConfig = "npm_local_scoped_repository_config.json"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 utils/tests/consts.go
@@ -97,6 +97,8 @@ const (
NpmLocalScopedRespositoryConfig = "npm_local_scoped_repository_config.json"
NpmRemoteRepositoryConfig = "npm_remote_repository_config.json"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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



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