diff --git a/cmd/flux/bootstrap_gitea.go b/cmd/flux/bootstrap_gitea.go index ad30e8bb..767f61a2 100644 --- a/cmd/flux/bootstrap_gitea.go +++ b/cmd/flux/bootstrap_gitea.go @@ -107,6 +107,11 @@ func init() { } func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error { + if bootstrapArgs.sshSigningReusePrivateKey { + return fmt.Errorf("--ssh-signing-reuse-private-key is not supported by 'bootstrap gitea'; " + + "that subcommand generates the SSH transport key in-process and has no operator-supplied key to reuse") + } + gtToken := os.Getenv(gtTokenEnvVar) if gtToken == "" { var err error @@ -254,11 +259,6 @@ func bootstrapGiteaCmdRun(cmd *cobra.Command, args []string) error { bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID), } - if bootstrapArgs.sshSigningReusePrivateKey { - return fmt.Errorf("--ssh-signing-reuse-private-key is not supported by 'bootstrap gitea'; " + - "that subcommand generates the SSH transport key in-process and has no operator-supplied key to reuse") - } - if bootstrapArgs.sshHostname != "" { bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname)) } diff --git a/cmd/flux/bootstrap_github.go b/cmd/flux/bootstrap_github.go index 6ae1cb9a..2867cd40 100644 --- a/cmd/flux/bootstrap_github.go +++ b/cmd/flux/bootstrap_github.go @@ -107,6 +107,11 @@ func init() { } func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { + if bootstrapArgs.sshSigningReusePrivateKey { + return fmt.Errorf("--ssh-signing-reuse-private-key is not supported by 'bootstrap github'; " + + "that subcommand generates the SSH transport key in-process and has no operator-supplied key to reuse") + } + ghToken := os.Getenv(ghTokenEnvVar) if ghToken == "" { var err error @@ -261,11 +266,6 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { bootstrap.WithGitCommitSigning(entityList, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID), } - if bootstrapArgs.sshSigningReusePrivateKey { - return fmt.Errorf("--ssh-signing-reuse-private-key is not supported by 'bootstrap github'; " + - "that subcommand generates the SSH transport key in-process and has no operator-supplied key to reuse") - } - if bootstrapArgs.sshHostname != "" { bootstrapOpts = append(bootstrapOpts, bootstrap.WithSSHHostname(bootstrapArgs.sshHostname)) } diff --git a/cmd/flux/bootstrap_test.go b/cmd/flux/bootstrap_test.go index 95b4e172..4ec9b84c 100644 --- a/cmd/flux/bootstrap_test.go +++ b/cmd/flux/bootstrap_test.go @@ -159,3 +159,50 @@ func TestBootstrapValidate_signingFlags(t *testing.T) { }) } } + +// Providers that generate the SSH transport key in-process (github, gitea) +// must reject --ssh-signing-reuse-private-key with their own, provider- +// specific error before bootstrapValidate runs — otherwise the generic +// "--ssh-signing-reuse-private-key requires --private-key-file" error +// shadows the fact that the flag is fundamentally unsupported there. +func TestBootstrapProviderRejectsReuseBeforeValidate(t *testing.T) { + tests := []struct { + name string + runE func() error + wantErr string + }{ + { + name: "github rejects reuse with provider-specific error", + runE: func() error { return bootstrapGitHubCmdRun(nil, nil) }, + wantErr: "not supported by 'bootstrap github'", + }, + { + name: "gitea rejects reuse with provider-specific error", + runE: func() error { return bootstrapGiteaCmdRun(nil, nil) }, + wantErr: "not supported by 'bootstrap gitea'", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + savedReuse := bootstrapArgs.sshSigningReusePrivateKey + savedPrivKey := bootstrapArgs.privateKeyFile + defer func() { + bootstrapArgs.sshSigningReusePrivateKey = savedReuse + bootstrapArgs.privateKeyFile = savedPrivKey + }() + + // Reuse flag set, no --private-key-file: bootstrapValidate + // would otherwise return "requires --private-key-file". + bootstrapArgs.sshSigningReusePrivateKey = true + bootstrapArgs.privateKeyFile = "" + + err := tt.runE() + if err == nil { + t.Fatalf("expected error containing %q, got nil", tt.wantErr) + } + if !strings.Contains(err.Error(), tt.wantErr) { + t.Fatalf("expected error containing %q, got: %v", tt.wantErr, err) + } + }) + } +}