Repository: regclient/regclient (go)

Issue:
[Issue] `regctl` segfaults when configuration file is a directory
<!--
Note: Please search to see if an issue already exists for the bug you encountered.
-->

### Current Behavior

`regctl` segfaults when `~/.regctl/config.json` or `REGCTL_CONFIG` is a directory. This is obviously a user error (I stumbled across this because of a docker mount creating `~/.regctl/config.json` as a directory), but `regctl` should probably give a proper error message instead of segfaulting (while regctl prints a warning, it seems there is still some error with dereferencing a null pointer).

### Expected Behavior

`regctl` should print an error message instead of segfaulting.

### Steps To Reproduce

```console
$ cd $(mktemp -d)
$ mkdir foo
$ REGCTL_CONFIG=$(pwd)/foo regctl image copy docker.io/alpine ocidir://alpine
WARN[0000] Failed to load default config                 err="read /tmp/tmp.7HYeA15ho3/foo: is a directory"
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0x8777f6]

goroutine 1 [running]:
main.(*rootCmd).newRegClient(0xc00028c000)
        github.com/regclient/regclient/cmd/regctl/root.go:138 +0x596
main.(*imageCmd).runImageCopy(0xc0002d0000, 0xc0002d2608, {0xc0002a4640, 0x0?, 0x0?})
        github.com/regclient/regclient/cmd/regctl/image.go:784 +0x1af
github.com/spf13/cobra.(*Command).execute(0xc0002d2608, {0xc0002a4600, 0x2, 0x2})
        github.com/spf13/cobra@v1.8.0/command.go:983 +0xaca
github.com/spf13/cobra.(*Command).ExecuteC(0xc00028e008)
        github.com/spf13/cobra@v1.8.0/command.go:1115 +0x3ff
github.com/spf13/cobra.(*Command).Execute(...)
        github.com/spf13/cobra@v1.8.0/command.go:1039
github.com/spf13/cobra.(*Command).ExecuteContext(...)
        github.com/spf13/cobra@v1.8.0/command.go:1032
main.main()
        github.com/regclient/regclient/cmd/regctl/main.go:29 +0x14e
```

### Version

```text
VCSTag:     v0.5.7
VCSRef:     unknown
VCSCommit:  unknown
VCSState:   unknown
VCSDate:    unknown
Platform:   linux/amd64
GoVer:      go1.22.2
GoCompiler: gc
```

### Environment

<!--
Please include:
- How you are running the tool: downloaded binary or from within a container
- Your platform: Linux, Windows, or Mac, x86 or ARM.
- Your registry: Docker Hub, ECR, GCR, ACR, Harbor, registry:2, etc.
-->

- Running as binary or container: binary
- Host platform: NixOS
- Registry description: n/a


Pull request description:
Fix: Handle nil pointer when config file is a dir
<!--

Commits must be signed indicating your agreement to the [DCO](https://developercertificate.org/).
See [DCO missing](https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md) for steps to fix a missing signoff.

-->

### Fixes issue

Fixes #737.
<!-- If this is a bug fix, include "fixes #xxxx", or "closes #xxxx" -->

### Describe the change

Most of the code immediately returns when `ConfigLoadDefault` errors. For the one place that doesn't, it needs to initialize the config with a default value instead of leaving a nil pointer.
<!-- Include the type of change: bug fix, new feature, breaking change, documentation update -->
<!-- Describe what was changed, why the change was made, and how it was implemented -->

### How to verify it

```shell
REGCTL_CONFIG=. regctl manifest get ghcr.io/regclient/regctl
```
<!-- Include steps that can be taken to verify the change -->

### Changelog text

- Fix: Handle nil pointer when config file is a directory.
<!-- If the release changelog should have an entry for this, include it here -->

### Please verify and check that the pull request fulfills the following requirements

<!-- Mark the following with an [X] to verify they are included -->

- [X] Tests have been added or not applicable
- [X] Documentation has been added, updated, or not applicable
- [X] Changes have been rebased to main
- [X] Multiple commits to the same code have been squashed

<!-- markdownlint-disable-file MD041 -->


Patch:
diff --git a/cmd/regctl/root.go b/cmd/regctl/root.go
index e64bfe5..7839d48 100644
--- a/cmd/regctl/root.go
+++ b/cmd/regctl/root.go
@@ -143,6 +143,9 @@ func (rootOpts *rootCmd) newRegClient() *regclient.RegClient {
 		log.WithFields(logrus.Fields{
 			"err": err,
 		}).Warn("Failed to load default config")
+		if conf == nil {
+			conf = ConfigNew()
+		}
 	}
 
 	rcOpts := []regclient.Opt{


Test patch:
diff --git a/cmd/regctl/root_test.go b/cmd/regctl/root_test.go
new file mode 100644
index 0000000..404efd1
--- /dev/null
+++ b/cmd/regctl/root_test.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+	"testing"
+)
+
+func TestRootConfigDir(t *testing.T) {
+	tmpDir := t.TempDir()
+
+	// This is invalid but should gracefully degrade and not crash
+	t.Setenv("REGCTL_CONFIG", tmpDir)
+
+	out, err := cobraTest(t, nil, "tag", "ls", "ocidir://../../testdata/testrepo")
+	if err != nil {
+		t.Fatalf("failed to list tags: %v", err)
+	}
+	if out == "" {
+		t.Errorf("missing output")
+	}
+}