Skip to content

Commit 0c16a2e

Browse files
committed
Register object signer in root pre-run
Entire-Checkpoint: 983194b9963d
1 parent 31a40b0 commit 0c16a2e

4 files changed

Lines changed: 120 additions & 82 deletions

File tree

cmd/entire/cli/objectsigner.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net"
7+
"os"
8+
"sync"
9+
10+
"github.com/go-git/go-git/v6/config"
11+
"github.com/go-git/go-git/v6/x/plugin"
12+
"github.com/go-git/x/plugin/objectsigner/auto"
13+
"golang.org/x/crypto/ssh/agent"
14+
)
15+
16+
var registerObjectSignerOnce sync.Once
17+
18+
func registerObjectSigner() {
19+
registerObjectSignerOnce.Do(func() {
20+
//nolint:errcheck,gosec // best-effort; if registration fails, commits are left unsigned
21+
plugin.Register(plugin.ObjectSigner(), func() plugin.Signer {
22+
cfgSource, err := plugin.Get(plugin.ConfigLoader())
23+
if err != nil {
24+
// No config loader registered; signing not possible.
25+
return nil
26+
}
27+
28+
sysCfg := loadScopedConfig(cfgSource, config.SystemScope)
29+
globalCfg := loadScopedConfig(cfgSource, config.GlobalScope)
30+
31+
// Merge system then global so that global settings take precedence.
32+
merged := config.Merge(sysCfg, globalCfg)
33+
34+
if !merged.Commit.GpgSign.IsTrue() {
35+
return nil
36+
}
37+
38+
cfg := auto.Config{
39+
SigningKey: merged.User.SigningKey,
40+
Format: auto.Format(merged.GPG.Format),
41+
SSHAgent: connectSSHAgent(),
42+
}
43+
44+
signer, err := auto.FromConfig(cfg)
45+
if err != nil {
46+
fmt.Fprintf(os.Stderr, "warning: failed to create object signer: %v\n", err)
47+
return nil
48+
}
49+
50+
return signer
51+
})
52+
})
53+
}
54+
55+
// connectSSHAgent connects to the SSH agent via SSH_AUTH_SOCK.
56+
// Returns nil if the agent is unavailable.
57+
func connectSSHAgent() agent.Agent { //nolint:ireturn // must return the ssh agent interface
58+
sock := os.Getenv("SSH_AUTH_SOCK")
59+
if sock == "" {
60+
return nil
61+
}
62+
63+
var d net.Dialer
64+
conn, err := d.DialContext(context.Background(), "unix", sock)
65+
if err != nil {
66+
return nil
67+
}
68+
69+
return agent.NewClient(conn)
70+
}
71+
72+
var scopeName = map[config.Scope]string{
73+
config.GlobalScope: "global",
74+
config.SystemScope: "system",
75+
}
76+
77+
func loadScopedConfig(source plugin.ConfigSource, scope config.Scope) *config.Config {
78+
name := scopeName[scope]
79+
80+
storer, err := source.Load(scope)
81+
if err != nil {
82+
fmt.Fprintf(os.Stderr, "warning: failed to load %s git config: %v\n", name, err)
83+
return config.NewConfig()
84+
}
85+
86+
cfg, err := storer.Config()
87+
if err != nil {
88+
fmt.Fprintf(os.Stderr, "warning: failed to parse %s git config: %v\n", name, err)
89+
return config.NewConfig()
90+
}
91+
92+
return cfg
93+
}

cmd/entire/cli/root.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ func NewRootCmd() *cobra.Command {
4141
CompletionOptions: cobra.CompletionOptions{
4242
HiddenDefaultCmd: true,
4343
},
44+
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
45+
registerObjectSigner()
46+
return nil
47+
},
4448
PersistentPostRun: func(cmd *cobra.Command, _ []string) {
4549
// Skip for hidden commands (walk parent chain — Cobra doesn't propagate Hidden)
4650
for c := cmd; c != nil; c = c.Parent() {

cmd/entire/cli/root_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import (
44
"bytes"
55
"runtime"
66
"strings"
7+
"sync"
78
"testing"
89

910
"github.com/entireio/cli/cmd/entire/cli/versioninfo"
11+
"github.com/go-git/go-git/v6/x/plugin"
1012
"github.com/spf13/cobra"
1113
)
1214

@@ -68,6 +70,27 @@ func TestVersionFlag_ContainsExpectedInfo(t *testing.T) {
6870
}
6971
}
7072

73+
func TestPersistentPreRun_RegistersObjectSigner(t *testing.T) {
74+
resetPluginEntry("object-signer")
75+
registerObjectSignerOnce = sync.Once{}
76+
t.Cleanup(func() {
77+
resetPluginEntry("object-signer")
78+
registerObjectSignerOnce = sync.Once{}
79+
})
80+
81+
root := NewRootCmd()
82+
root.SetOut(&bytes.Buffer{})
83+
root.SetErr(&bytes.Buffer{})
84+
root.SetArgs([]string{"version"})
85+
if err := root.Execute(); err != nil {
86+
t.Fatalf("entire version failed: %v", err)
87+
}
88+
89+
if !plugin.Has(plugin.ObjectSigner()) {
90+
t.Fatal("expected object signer plugin to be registered during PersistentPreRunE")
91+
}
92+
}
93+
7194
func TestPersistentPostRun_SkipsHiddenParent(t *testing.T) {
7295
t.Parallel()
7396

cmd/entire/main.go

Lines changed: 0 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,17 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"net"
87
"os"
98
"os/signal"
109
"runtime"
1110
"strings"
1211
"syscall"
1312

1413
"github.com/entireio/cli/cmd/entire/cli"
15-
"github.com/go-git/go-git/v6/config"
16-
"github.com/go-git/go-git/v6/x/plugin"
17-
"github.com/go-git/x/plugin/objectsigner/auto"
1814
"github.com/spf13/cobra"
19-
"golang.org/x/crypto/ssh/agent"
2015
)
2116

2217
func main() {
23-
registerObjectSigner()
24-
2518
// Create context that cancels on interrupt
2619
ctx, cancel := context.WithCancel(context.Background())
2720

@@ -58,81 +51,6 @@ func main() {
5851
cancel() // Cleanup on successful exit
5952
}
6053

61-
func registerObjectSigner() {
62-
//nolint:errcheck,gosec // best-effort; if registration fails, commits are left unsigned
63-
plugin.Register(plugin.ObjectSigner(), func() plugin.Signer {
64-
cfgSource, err := plugin.Get(plugin.ConfigLoader())
65-
if err != nil {
66-
// No config loader registered; signing not possible.
67-
return nil
68-
}
69-
70-
sysCfg := loadScopedConfig(cfgSource, config.SystemScope)
71-
globalCfg := loadScopedConfig(cfgSource, config.GlobalScope)
72-
73-
// Merge system then global so that global settings take precedence.
74-
merged := config.Merge(sysCfg, globalCfg)
75-
76-
if !merged.Commit.GpgSign.IsTrue() {
77-
return nil
78-
}
79-
80-
cfg := auto.Config{
81-
SigningKey: merged.User.SigningKey,
82-
Format: auto.Format(merged.GPG.Format),
83-
SSHAgent: connectSSHAgent(),
84-
}
85-
86-
signer, err := auto.FromConfig(cfg)
87-
if err != nil {
88-
fmt.Fprintf(os.Stderr, "warning: failed to create object signer: %v\n", err)
89-
return nil
90-
}
91-
92-
return signer
93-
})
94-
}
95-
96-
// connectSSHAgent connects to the SSH agent via SSH_AUTH_SOCK.
97-
// Returns nil if the agent is unavailable.
98-
func connectSSHAgent() agent.Agent { //nolint:ireturn // must return the ssh agent interface
99-
sock := os.Getenv("SSH_AUTH_SOCK")
100-
if sock == "" {
101-
return nil
102-
}
103-
104-
var d net.Dialer
105-
conn, err := d.DialContext(context.Background(), "unix", sock)
106-
if err != nil {
107-
return nil
108-
}
109-
110-
return agent.NewClient(conn)
111-
}
112-
113-
var scopeName = map[config.Scope]string{
114-
config.GlobalScope: "global",
115-
config.SystemScope: "system",
116-
}
117-
118-
func loadScopedConfig(source plugin.ConfigSource, scope config.Scope) *config.Config {
119-
name := scopeName[scope]
120-
121-
storer, err := source.Load(scope)
122-
if err != nil {
123-
fmt.Fprintf(os.Stderr, "warning: failed to load %s git config: %v\n", name, err)
124-
return config.NewConfig()
125-
}
126-
127-
cfg, err := storer.Config()
128-
if err != nil {
129-
fmt.Fprintf(os.Stderr, "warning: failed to parse %s git config: %v\n", name, err)
130-
return config.NewConfig()
131-
}
132-
133-
return cfg
134-
}
135-
13654
func showSuggestion(cmd *cobra.Command, err error) {
13755
// Print usage first (brew style)
13856
fmt.Fprint(cmd.OutOrStderr(), cmd.UsageString())

0 commit comments

Comments
 (0)