From d281b11af7506dd14a78e3947c647eba57e7d0ba Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Sun, 18 May 2025 16:51:48 +0200 Subject: [PATCH 01/18] migrate to urfave v3 --- cmd/actions.go | 7 +-- cmd/admin.go | 10 ++-- cmd/admin_auth.go | 7 +-- cmd/admin_auth_ldap.go | 32 +++++------ cmd/admin_auth_ldap_test.go | 40 +++++++------- cmd/admin_auth_oauth.go | 9 ++-- cmd/admin_auth_stmp.go | 9 ++-- cmd/admin_regenerate.go | 8 +-- cmd/admin_user.go | 4 +- cmd/admin_user_change_password.go | 5 +- cmd/admin_user_create.go | 13 +++-- cmd/admin_user_create_test.go | 4 +- cmd/admin_user_delete.go | 5 +- cmd/admin_user_generate_access_token.go | 5 +- cmd/admin_user_list.go | 5 +- cmd/admin_user_must_change_password.go | 5 +- cmd/cert.go | 5 +- cmd/cmd.go | 12 ++--- cmd/docs.go | 19 ++++--- cmd/doctor.go | 38 ++++++------- cmd/doctor_convert.go | 5 +- cmd/doctor_test.go | 13 ++--- cmd/dump.go | 31 +++++------ cmd/dump_repo.go | 24 ++++----- cmd/embedded.go | 21 ++++---- cmd/generate.go | 13 ++--- cmd/hook.go | 12 ++--- cmd/keys.go | 7 +-- cmd/mailer.go | 5 +- cmd/main.go | 53 ++++++++++--------- cmd/main_test.go | 21 ++++---- cmd/manager.go | 15 +++--- cmd/manager_logging.go | 23 ++++---- cmd/migrate.go | 4 +- cmd/migrate_storage.go | 44 +++++++-------- cmd/restore_repo.go | 5 +- cmd/serv.go | 4 +- cmd/web.go | 28 +++++----- contrib/backport/backport.go | 10 ++-- .../environment-to-ini/environment-to-ini.go | 9 ++-- go.mod | 6 +-- go.sum | 12 ++--- main.go | 2 +- tests/integration/cmd_keys_test.go | 11 ++-- 44 files changed, 326 insertions(+), 294 deletions(-) diff --git a/cmd/actions.go b/cmd/actions.go index f582c16c81c48..240e17f35ecdd 100644 --- a/cmd/actions.go +++ b/cmd/actions.go @@ -4,12 +4,13 @@ package cmd import ( + "context" "fmt" "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( @@ -17,7 +18,7 @@ var ( CmdActions = &cli.Command{ Name: "actions", Usage: "Manage Gitea Actions", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ subcmdActionsGenRunnerToken, }, } @@ -38,7 +39,7 @@ var ( } ) -func runGenerateActionsRunnerToken(c *cli.Context) error { +func runGenerateActionsRunnerToken(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() diff --git a/cmd/admin.go b/cmd/admin.go index 6c9480e76eb7a..32baa625aaeca 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -15,7 +15,7 @@ import ( "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( @@ -23,7 +23,7 @@ var ( CmdAdmin = &cli.Command{ Name: "admin", Usage: "Perform common administrative operations", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ subcmdUser, subcmdRepoSyncReleases, subcmdRegenerate, @@ -41,7 +41,7 @@ var ( subcmdRegenerate = &cli.Command{ Name: "regenerate", Usage: "Regenerate specific files", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ microcmdRegenHooks, microcmdRegenKeys, }, @@ -50,7 +50,7 @@ var ( subcmdAuth = &cli.Command{ Name: "auth", Usage: "Modify external auth providers", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ microcmdAuthAddOauth, microcmdAuthUpdateOauth, microcmdAuthAddLdapBindDn, @@ -93,7 +93,7 @@ var ( } ) -func runRepoSyncReleases(_ *cli.Context) error { +func runRepoSyncReleases(_ context.Context, _ *cli.Command) error { ctx, cancel := installSignals() defer cancel() diff --git a/cmd/admin_auth.go b/cmd/admin_auth.go index 4777a9290867c..eff1532b2f694 100644 --- a/cmd/admin_auth.go +++ b/cmd/admin_auth.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "os" @@ -13,7 +14,7 @@ import ( "code.gitea.io/gitea/models/db" auth_service "code.gitea.io/gitea/services/auth" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( @@ -56,7 +57,7 @@ var ( } ) -func runListAuth(c *cli.Context) error { +func runListAuth(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() @@ -90,7 +91,7 @@ func runListAuth(c *cli.Context) error { return nil } -func runDeleteAuth(c *cli.Context) error { +func runDeleteAuth(_ context.Context, c *cli.Command) error { if !c.IsSet("id") { return errors.New("--id flag is missing") } diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index d2eeb7c0d6d6f..740329435547e 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -12,7 +12,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/auth/source/ldap" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) type ( @@ -167,8 +167,8 @@ var ( microcmdAuthAddLdapBindDn = &cli.Command{ Name: "add-ldap", Usage: "Add new LDAP (via Bind DN) authentication source", - Action: func(c *cli.Context) error { - return newAuthService().addLdapBindDn(c) + Action: func(ctx context.Context, cmd *cli.Command) error { + return newAuthService().addLdapBindDn(ctx, cmd) }, Flags: ldapBindDnCLIFlags, } @@ -176,8 +176,8 @@ var ( microcmdAuthUpdateLdapBindDn = &cli.Command{ Name: "update-ldap", Usage: "Update existing LDAP (via Bind DN) authentication source", - Action: func(c *cli.Context) error { - return newAuthService().updateLdapBindDn(c) + Action: func(ctx context.Context, cmd *cli.Command) error { + return newAuthService().updateLdapBindDn(ctx, cmd) }, Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...), } @@ -185,8 +185,8 @@ var ( microcmdAuthAddLdapSimpleAuth = &cli.Command{ Name: "add-ldap-simple", Usage: "Add new LDAP (simple auth) authentication source", - Action: func(c *cli.Context) error { - return newAuthService().addLdapSimpleAuth(c) + Action: func(ctx context.Context, cmd *cli.Command) error { + return newAuthService().addLdapSimpleAuth(ctx, cmd) }, Flags: ldapSimpleAuthCLIFlags, } @@ -194,8 +194,8 @@ var ( microcmdAuthUpdateLdapSimpleAuth = &cli.Command{ Name: "update-ldap-simple", Usage: "Update existing LDAP (simple auth) authentication source", - Action: func(c *cli.Context) error { - return newAuthService().updateLdapSimpleAuth(c) + Action: func(ctx context.Context, cmd *cli.Command) error { + return newAuthService().updateLdapSimpleAuth(ctx, cmd) }, Flags: append([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...), } @@ -212,7 +212,7 @@ func newAuthService() *authService { } // parseAuthSourceLdap assigns values on authSource according to command line flags. -func parseAuthSourceLdap(c *cli.Context, authSource *auth.Source) { +func parseAuthSourceLdap(c *cli.Command, authSource *auth.Source) { if c.IsSet("name") { authSource.Name = c.String("name") } @@ -232,7 +232,7 @@ func parseAuthSourceLdap(c *cli.Context, authSource *auth.Source) { } // parseLdapConfig assigns values on config according to command line flags. -func parseLdapConfig(c *cli.Context, config *ldap.Source) error { +func parseLdapConfig(c *cli.Command, config *ldap.Source) error { if c.IsSet("name") { config.Name = c.String("name") } @@ -337,7 +337,7 @@ func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) { // getAuthSource gets the login source by its id defined in the command line flags. // It returns an error if the id is not set, does not match any source or if the source is not of expected type. -func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authType auth.Type) (*auth.Source, error) { +func (a *authService) getAuthSource(ctx context.Context, c *cli.Command, authType auth.Type) (*auth.Source, error) { if err := argsSet(c, "id"); err != nil { return nil, err } @@ -355,7 +355,7 @@ func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authTyp } // addLdapBindDn adds a new LDAP via Bind DN authentication source. -func (a *authService) addLdapBindDn(c *cli.Context) error { +func (a *authService) addLdapBindDn(_ context.Context, c *cli.Command) error { if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil { return err } @@ -384,7 +384,7 @@ func (a *authService) addLdapBindDn(c *cli.Context) error { } // updateLdapBindDn updates a new LDAP via Bind DN authentication source. -func (a *authService) updateLdapBindDn(c *cli.Context) error { +func (a *authService) updateLdapBindDn(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() @@ -406,7 +406,7 @@ func (a *authService) updateLdapBindDn(c *cli.Context) error { } // addLdapSimpleAuth adds a new LDAP (simple auth) authentication source. -func (a *authService) addLdapSimpleAuth(c *cli.Context) error { +func (a *authService) addLdapSimpleAuth(_ context.Context, c *cli.Command) error { if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil { return err } @@ -435,7 +435,7 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error { } // updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source. -func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { +func (a *authService) updateLdapSimpleAuth(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go index ea9a83ef76dbd..49061afdabb1a 100644 --- a/cmd/admin_auth_ldap_test.go +++ b/cmd/admin_auth_ldap_test.go @@ -11,7 +11,7 @@ import ( "code.gitea.io/gitea/services/auth/source/ldap" "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func TestAddLdapBindDn(t *testing.T) { @@ -239,12 +239,13 @@ func TestAddLdapBindDn(t *testing.T) { } // Create a copy of command to test - app := cli.NewApp() - app.Flags = microcmdAuthAddLdapBindDn.Flags - app.Action = service.addLdapBindDn + app := cli.Command{ + Flags: microcmdAuthAddLdapBindDn.Flags, + Action: service.addLdapBindDn, + } // Run it - err := app.Run(c.args) + err := app.Run(t.Context(), c.args) if c.errMsg != "" { assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) } else { @@ -470,12 +471,13 @@ func TestAddLdapSimpleAuth(t *testing.T) { } // Create a copy of command to test - app := cli.NewApp() - app.Flags = microcmdAuthAddLdapSimpleAuth.Flags - app.Action = service.addLdapSimpleAuth + app := &cli.Command{ + Flags: microcmdAuthAddLdapSimpleAuth.Flags, + Action: service.addLdapSimpleAuth, + } // Run it - err := app.Run(c.args) + err := app.Run(t.Context(), c.args) if c.errMsg != "" { assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) } else { @@ -947,12 +949,12 @@ func TestUpdateLdapBindDn(t *testing.T) { } // Create a copy of command to test - app := cli.NewApp() - app.Flags = microcmdAuthUpdateLdapBindDn.Flags - app.Action = service.updateLdapBindDn - + app := cli.Command{ + Flags: microcmdAuthUpdateLdapBindDn.Flags, + Action: service.updateLdapBindDn, + } // Run it - err := app.Run(c.args) + err := app.Run(t.Context(), c.args) if c.errMsg != "" { assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) } else { @@ -1337,12 +1339,12 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { } // Create a copy of command to test - app := cli.NewApp() - app.Flags = microcmdAuthUpdateLdapSimpleAuth.Flags - app.Action = service.updateLdapSimpleAuth - + app := cli.Command{ + Flags: microcmdAuthUpdateLdapSimpleAuth.Flags, + Action: service.updateLdapSimpleAuth, + } // Run it - err := app.Run(c.args) + err := app.Run(t.Context(), c.args) if c.errMsg != "" { assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) } else { diff --git a/cmd/admin_auth_oauth.go b/cmd/admin_auth_oauth.go index be5345d992855..091531686620a 100644 --- a/cmd/admin_auth_oauth.go +++ b/cmd/admin_auth_oauth.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "net/url" @@ -12,7 +13,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/auth/source/oauth2" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( @@ -137,7 +138,7 @@ var ( } ) -func parseOAuth2Config(c *cli.Context) *oauth2.Source { +func parseOAuth2Config(c *cli.Command) *oauth2.Source { var customURLMapping *oauth2.CustomURLMapping if c.IsSet("use-custom-urls") { customURLMapping = &oauth2.CustomURLMapping{ @@ -168,7 +169,7 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source { } } -func runAddOauth(c *cli.Context) error { +func runAddOauth(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() @@ -193,7 +194,7 @@ func runAddOauth(c *cli.Context) error { }) } -func runUpdateOauth(c *cli.Context) error { +func runUpdateOauth(_ context.Context, c *cli.Command) error { if !c.IsSet("id") { return errors.New("--id flag is missing") } diff --git a/cmd/admin_auth_stmp.go b/cmd/admin_auth_stmp.go index babcf78ceae4a..016cb5639a51d 100644 --- a/cmd/admin_auth_stmp.go +++ b/cmd/admin_auth_stmp.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "strings" @@ -11,7 +12,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/auth/source/smtp" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( @@ -87,7 +88,7 @@ var ( } ) -func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error { +func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error { if c.IsSet("auth-type") { conf.Auth = c.String("auth-type") validAuthTypes := []string{"PLAIN", "LOGIN", "CRAM-MD5"} @@ -120,7 +121,7 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error { return nil } -func runAddSMTP(c *cli.Context) error { +func runAddSMTP(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() @@ -161,7 +162,7 @@ func runAddSMTP(c *cli.Context) error { }) } -func runUpdateSMTP(c *cli.Context) error { +func runUpdateSMTP(_ context.Context, c *cli.Command) error { if !c.IsSet("id") { return errors.New("--id flag is missing") } diff --git a/cmd/admin_regenerate.go b/cmd/admin_regenerate.go index ab769f6d0c6f3..a5c93364f8087 100644 --- a/cmd/admin_regenerate.go +++ b/cmd/admin_regenerate.go @@ -4,11 +4,13 @@ package cmd import ( + "context" + "code.gitea.io/gitea/modules/graceful" asymkey_service "code.gitea.io/gitea/services/asymkey" repo_service "code.gitea.io/gitea/services/repository" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( @@ -25,7 +27,7 @@ var ( } ) -func runRegenerateHooks(_ *cli.Context) error { +func runRegenerateHooks(_ context.Context, _ *cli.Command) error { ctx, cancel := installSignals() defer cancel() @@ -35,7 +37,7 @@ func runRegenerateHooks(_ *cli.Context) error { return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext()) } -func runRegenerateKeys(_ *cli.Context) error { +func runRegenerateKeys(_ context.Context, _ *cli.Command) error { ctx, cancel := installSignals() defer cancel() diff --git a/cmd/admin_user.go b/cmd/admin_user.go index 967a6ed88a22a..386c611270c6d 100644 --- a/cmd/admin_user.go +++ b/cmd/admin_user.go @@ -4,13 +4,13 @@ package cmd import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var subcmdUser = &cli.Command{ Name: "user", Usage: "Modify users", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ microcmdUserCreate, microcmdUserList, microcmdUserChangePassword, diff --git a/cmd/admin_user_change_password.go b/cmd/admin_user_change_password.go index f1ed46e70b083..f7c05ad647679 100644 --- a/cmd/admin_user_change_password.go +++ b/cmd/admin_user_change_password.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" @@ -13,7 +14,7 @@ import ( "code.gitea.io/gitea/modules/setting" user_service "code.gitea.io/gitea/services/user" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var microcmdUserChangePassword = &cli.Command{ @@ -41,7 +42,7 @@ var microcmdUserChangePassword = &cli.Command{ }, } -func runChangePassword(c *cli.Context) error { +func runChangePassword(_ context.Context, c *cli.Command) error { if err := argsSet(c, "username", "password"); err != nil { return err } diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 97f9bb7f06a3e..864b2a670dba9 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -16,7 +16,7 @@ import ( "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var microcmdUserCreate = &cli.Command{ @@ -54,9 +54,9 @@ var microcmdUserCreate = &cli.Command{ Usage: "Generate a random password for the user", }, &cli.BoolFlag{ - Name: "must-change-password", - Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)", - DisableDefaultText: true, + Name: "must-change-password", + Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)", + DefaultText: "", }, &cli.IntFlag{ Name: "random-password-length", @@ -88,7 +88,7 @@ var microcmdUserCreate = &cli.Command{ }, } -func runCreateUser(c *cli.Context) error { +func runCreateUser(ctx context.Context, c *cli.Command) error { // this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first // duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future. setting.LoadSettings() @@ -129,10 +129,9 @@ func runCreateUser(c *cli.Context) error { username = c.String("username") } else { username = c.String("name") - _, _ = fmt.Fprintf(c.App.ErrWriter, "--name flag is deprecated. Use --username instead.\n") + _, _ = fmt.Fprintf(c.ErrWriter, "--name flag is deprecated. Use --username instead.\n") } - ctx := c.Context if !setting.IsInTesting { // FIXME: need to refactor the "installSignals/initDB" related code later // it doesn't make sense to call it in (almost) every command action function diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go index d5952412c304c..5b95b40196347 100644 --- a/cmd/admin_user_create_test.go +++ b/cmd/admin_user_create_test.go @@ -32,7 +32,7 @@ func TestAdminUserCreate(t *testing.T) { MustChangePassword bool } createCheck := func(name, args string) check { - require.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args)))) + require.NoError(t, app.Run(t.Context(), strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args)))) u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name}) return check{IsAdmin: u.IsAdmin, MustChangePassword: u.MustChangePassword} } @@ -51,7 +51,7 @@ func TestAdminUserCreate(t *testing.T) { }) createUser := func(name string, args ...string) error { - return app.Run(append([]string{"./gitea", "admin", "user", "create", "--username", name, "--email", name + "@gitea.local"}, args...)) + return app.Run(t.Context(), append([]string{"./gitea", "admin", "user", "create", "--username", name, "--email", name + "@gitea.local"}, args...)) } t.Run("UserType", func(t *testing.T) { diff --git a/cmd/admin_user_delete.go b/cmd/admin_user_delete.go index 520557554a215..18317622cf330 100644 --- a/cmd/admin_user_delete.go +++ b/cmd/admin_user_delete.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "strings" @@ -12,7 +13,7 @@ import ( "code.gitea.io/gitea/modules/storage" user_service "code.gitea.io/gitea/services/user" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var microcmdUserDelete = &cli.Command{ @@ -41,7 +42,7 @@ var microcmdUserDelete = &cli.Command{ Action: runDeleteUser, } -func runDeleteUser(c *cli.Context) error { +func runDeleteUser(_ context.Context, c *cli.Command) error { if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") { return errors.New("You must provide the id, username or email of a user to delete") } diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go index f6db7a74bd1ec..901ac5e1cbc26 100644 --- a/cmd/admin_user_generate_access_token.go +++ b/cmd/admin_user_generate_access_token.go @@ -4,13 +4,14 @@ package cmd import ( + "context" "errors" "fmt" auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var microcmdUserGenerateAccessToken = &cli.Command{ @@ -41,7 +42,7 @@ var microcmdUserGenerateAccessToken = &cli.Command{ Action: runGenerateAccessToken, } -func runGenerateAccessToken(c *cli.Context) error { +func runGenerateAccessToken(_ context.Context, c *cli.Command) error { if !c.IsSet("username") { return errors.New("you must provide a username to generate a token for") } diff --git a/cmd/admin_user_list.go b/cmd/admin_user_list.go index 4c2b26d1dfd97..ec214345fc3dd 100644 --- a/cmd/admin_user_list.go +++ b/cmd/admin_user_list.go @@ -4,13 +4,14 @@ package cmd import ( + "context" "fmt" "os" "text/tabwriter" user_model "code.gitea.io/gitea/models/user" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var microcmdUserList = &cli.Command{ @@ -25,7 +26,7 @@ var microcmdUserList = &cli.Command{ }, } -func runListUsers(c *cli.Context) error { +func runListUsers(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() diff --git a/cmd/admin_user_must_change_password.go b/cmd/admin_user_must_change_password.go index 2794414259ac4..ddc82af4161aa 100644 --- a/cmd/admin_user_must_change_password.go +++ b/cmd/admin_user_must_change_password.go @@ -4,12 +4,13 @@ package cmd import ( + "context" "errors" "fmt" user_model "code.gitea.io/gitea/models/user" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var microcmdUserMustChangePassword = &cli.Command{ @@ -34,7 +35,7 @@ var microcmdUserMustChangePassword = &cli.Command{ }, } -func runMustChangePassword(c *cli.Context) error { +func runMustChangePassword(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() diff --git a/cmd/cert.go b/cmd/cert.go index 38241d71a3375..7ab2c843774d3 100644 --- a/cmd/cert.go +++ b/cmd/cert.go @@ -6,6 +6,7 @@ package cmd import ( + "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -20,7 +21,7 @@ import ( "strings" "time" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdCert represents the available cert sub-command. @@ -89,7 +90,7 @@ func pemBlockForKey(priv any) *pem.Block { } } -func runCert(c *cli.Context) error { +func runCert(_ context.Context, c *cli.Command) error { if err := argsSet(c, "host"); err != nil { return err } diff --git a/cmd/cmd.go b/cmd/cmd.go index 423dce26748e7..afd9b3124e83b 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -20,12 +20,12 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // argsSet checks that all the required arguments are set. args is a list of // arguments that must be set in the passed Context. -func argsSet(c *cli.Context, args ...string) error { +func argsSet(c *cli.Command, args ...string) error { for _, a := range args { if !c.IsSet(a) { return errors.New(a + " is not set") @@ -109,7 +109,7 @@ func setupConsoleLogger(level log.Level, colorize bool, out io.Writer) { log.GetManager().GetLogger(log.DEFAULT).ReplaceAllWriters(writer) } -func globalBool(c *cli.Context, name string) bool { +func globalBool(c *cli.Command, name string) bool { for _, ctx := range c.Lineage() { if ctx.Bool(name) { return true @@ -120,8 +120,8 @@ func globalBool(c *cli.Context, name string) bool { // PrepareConsoleLoggerLevel by default, use INFO level for console logger, but some sub-commands (for git/ssh protocol) shouldn't output any log to stdout. // Any log appears in git stdout pipe will break the git protocol, eg: client can't push and hangs forever. -func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error { - return func(c *cli.Context) error { +func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(context.Context, *cli.Command) (context.Context, error) { + return func(ctx context.Context, c *cli.Command) (context.Context, error) { level := defaultLevel if globalBool(c, "quiet") { level = log.FATAL @@ -130,6 +130,6 @@ func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(*cli.Context) error level = log.TRACE } log.SetConsoleLogger(log.DEFAULT, "console-default", level) - return nil + return ctx, nil } } diff --git a/cmd/docs.go b/cmd/docs.go index 605d02e3efeb5..380b063448b53 100644 --- a/cmd/docs.go +++ b/cmd/docs.go @@ -4,11 +4,14 @@ package cmd import ( + "context" "fmt" "os" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" + + cli_docs "github.com/urfave/cli-docs/v3" ) // CmdDocs represents the available docs sub-command. @@ -30,16 +33,16 @@ var CmdDocs = &cli.Command{ }, } -func runDocs(ctx *cli.Context) error { - docs, err := ctx.App.ToMarkdown() - if ctx.Bool("man") { - docs, err = ctx.App.ToMan() +func runDocs(_ context.Context, cmd *cli.Command) error { + docs, err := cli_docs.ToMarkdown(cmd) + if cmd.Bool("man") { + docs, err = cli_docs.ToMan(cmd) } if err != nil { return err } - if !ctx.Bool("man") { + if !cmd.Bool("man") { // Clean up markdown. The following bug was fixed in v2, but is present in v1. // It affects markdown output (even though the issue is referring to man pages) // https://github.com/urfave/cli/issues/1040 @@ -51,8 +54,8 @@ func runDocs(ctx *cli.Context) error { } out := os.Stdout - if ctx.String("output") != "" { - fi, err := os.Create(ctx.String("output")) + if cmd.String("output") != "" { + fi, err := os.Create(cmd.String("output")) if err != nil { return err } diff --git a/cmd/doctor.go b/cmd/doctor.go index 4a12b957f50df..688793bcfa9a7 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -20,7 +20,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/doctor" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "xorm.io/xorm" ) @@ -30,7 +30,7 @@ var CmdDoctor = &cli.Command{ Usage: "Diagnose and optionally fix problems, convert or re-create database tables", Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ cmdDoctorCheck, cmdRecreateTable, cmdDoctorConvert, @@ -93,7 +93,7 @@ You should back-up your database before doing this and ensure that your database Action: runRecreateTable, } -func runRecreateTable(ctx *cli.Context) error { +func runRecreateTable(_ context.Context, cmd *cli.Command) error { stdCtx, cancel := installSignals() defer cancel() @@ -102,7 +102,7 @@ func runRecreateTable(ctx *cli.Context) error { golog.SetPrefix("") golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info)) - debug := ctx.Bool("debug") + debug := cmd.Bool("debug") setting.MustInstalled() setting.LoadDBSetting() @@ -119,9 +119,9 @@ func runRecreateTable(ctx *cli.Context) error { return nil } - args := ctx.Args() - names := make([]string, 0, ctx.NArg()) - for i := 0; i < ctx.NArg(); i++ { + args := cmd.Args() + names := make([]string, 0, cmd.NArg()) + for i := 0; i < cmd.NArg(); i++ { names = append(names, args.Get(i)) } @@ -139,11 +139,11 @@ func runRecreateTable(ctx *cli.Context) error { }) } -func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) { +func setupDoctorDefaultLogger(cmd *cli.Command, colorize bool) { // Silence the default loggers setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr) - logFile := ctx.String("log-file") + logFile := cmd.String("log-file") switch logFile { case "": return // if no doctor log-file is set, do not show any log from default logger @@ -161,23 +161,23 @@ func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) { } } -func runDoctorCheck(ctx *cli.Context) error { +func runDoctorCheck(_ context.Context, cmd *cli.Command) error { stdCtx, cancel := installSignals() defer cancel() colorize := log.CanColorStdout - if ctx.IsSet("color") { - colorize = ctx.Bool("color") + if cmd.IsSet("color") { + colorize = cmd.Bool("color") } - setupDoctorDefaultLogger(ctx, colorize) + setupDoctorDefaultLogger(cmd, colorize) // Finally redirect the default golang's log to here golog.SetFlags(0) golog.SetPrefix("") golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info)) - if ctx.IsSet("list") { + if cmd.IsSet("list") { w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) _, _ = w.Write([]byte("Default\tName\tTitle\n")) doctor.SortChecks(doctor.Checks) @@ -195,12 +195,12 @@ func runDoctorCheck(ctx *cli.Context) error { } var checks []*doctor.Check - if ctx.Bool("all") { + if cmd.Bool("all") { checks = make([]*doctor.Check, len(doctor.Checks)) copy(checks, doctor.Checks) - } else if ctx.IsSet("run") { - addDefault := ctx.Bool("default") - runNamesSet := container.SetOf(ctx.StringSlice("run")...) + } else if cmd.IsSet("run") { + addDefault := cmd.Bool("default") + runNamesSet := container.SetOf(cmd.StringSlice("run")...) for _, check := range doctor.Checks { if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) { checks = append(checks, check) @@ -217,5 +217,5 @@ func runDoctorCheck(ctx *cli.Context) error { } } } - return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks) + return doctor.RunChecks(stdCtx, colorize, cmd.Bool("fix"), checks) } diff --git a/cmd/doctor_convert.go b/cmd/doctor_convert.go index 48c835ad0e2eb..deb989128e613 100644 --- a/cmd/doctor_convert.go +++ b/cmd/doctor_convert.go @@ -4,13 +4,14 @@ package cmd import ( + "context" "fmt" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // cmdDoctorConvert represents the available convert sub-command. @@ -21,7 +22,7 @@ var cmdDoctorConvert = &cli.Command{ Action: runDoctorConvert, } -func runDoctorConvert(ctx *cli.Context) error { +func runDoctorConvert(_ context.Context, cmd *cli.Command) error { stdCtx, cancel := installSignals() defer cancel() diff --git a/cmd/doctor_test.go b/cmd/doctor_test.go index 3e1ff299c5a4a..da942b38b600b 100644 --- a/cmd/doctor_test.go +++ b/cmd/doctor_test.go @@ -11,7 +11,7 @@ import ( "code.gitea.io/gitea/services/doctor" "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func TestDoctorRun(t *testing.T) { @@ -22,12 +22,13 @@ func TestDoctorRun(t *testing.T) { SkipDatabaseInitialization: true, }) - app := cli.NewApp() - app.Commands = []*cli.Command{cmdDoctorCheck} - err := app.Run([]string{"./gitea", "check", "--run", "test-check"}) + app := &cli.Command{ + Commands: []*cli.Command{cmdDoctorCheck}, + } + err := app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check"}) assert.NoError(t, err) - err = app.Run([]string{"./gitea", "check", "--run", "no-such"}) + err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "no-such"}) assert.ErrorContains(t, err, `unknown checks: "no-such"`) - err = app.Run([]string{"./gitea", "check", "--run", "test-check,no-such"}) + err = app.Run(t.Context(), []string{"./gitea", "check", "--run", "test-check,no-such"}) assert.ErrorContains(t, err, `unknown checks: "no-such"`) } diff --git a/cmd/dump.go b/cmd/dump.go index 7d640b78fdfc0..8ae278bd7040e 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -5,6 +5,7 @@ package cmd import ( + "context" "os" "path" "path/filepath" @@ -20,7 +21,7 @@ import ( "gitea.com/go-chi/session" "github.com/mholt/archiver/v3" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdDump represents the available dump sub-command. @@ -101,17 +102,17 @@ func fatal(format string, args ...any) { log.Fatal(format, args...) } -func runDump(ctx *cli.Context) error { +func runDump(_ context.Context, cmd *cli.Command) error { setting.MustInstalled() - quite := ctx.Bool("quiet") - verbose := ctx.Bool("verbose") + quite := cmd.Bool("quiet") + verbose := cmd.Bool("verbose") if verbose && quite { fatal("Option --quiet and --verbose cannot both be set") } // outFileName is either "-" or a file name (will be made absolute) - outFileName, outType := dump.PrepareFileNameAndType(ctx.String("file"), ctx.String("type")) + outFileName, outType := dump.PrepareFileNameAndType(cmd.String("file"), cmd.String("type")) if outType == "" { fatal("Invalid output type") } @@ -165,7 +166,7 @@ func runDump(ctx *cli.Context) error { } dumper.GlobalExcludeAbsPath(outFileName) - if ctx.IsSet("skip-repository") && ctx.Bool("skip-repository") { + if cmd.IsSet("skip-repository") && cmd.Bool("skip-repository") { log.Info("Skip dumping local repositories") } else { log.Info("Dumping local repositories... %s", setting.RepoRootPath) @@ -173,7 +174,7 @@ func runDump(ctx *cli.Context) error { fatal("Failed to include repositories: %v", err) } - if ctx.IsSet("skip-lfs-data") && ctx.Bool("skip-lfs-data") { + if cmd.IsSet("skip-lfs-data") && cmd.Bool("skip-lfs-data") { log.Info("Skip dumping LFS data") } else if !setting.LFS.StartServer { log.Info("LFS isn't enabled. Skip dumping LFS data") @@ -188,12 +189,12 @@ func runDump(ctx *cli.Context) error { } } - if ctx.Bool("skip-db") { + if cmd.Bool("skip-db") { // Ensure that we don't dump the database file that may reside in setting.AppDataPath or elsewhere. dumper.GlobalExcludeAbsPath(setting.Database.Path) log.Info("Skipping database") } else { - tmpDir := ctx.String("tempdir") + tmpDir := cmd.String("tempdir") if _, err := os.Stat(tmpDir); os.IsNotExist(err) { fatal("Path does not exist: %s", tmpDir) } @@ -209,7 +210,7 @@ func runDump(ctx *cli.Context) error { } }() - targetDBType := ctx.String("database") + targetDBType := cmd.String("database") if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() { log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType) } else { @@ -230,7 +231,7 @@ func runDump(ctx *cli.Context) error { fatal("Failed to include specified app.ini: %v", err) } - if ctx.IsSet("skip-custom-dir") && ctx.Bool("skip-custom-dir") { + if cmd.IsSet("skip-custom-dir") && cmd.Bool("skip-custom-dir") { log.Info("Skipping custom directory") } else { customDir, err := os.Stat(setting.CustomPath) @@ -263,7 +264,7 @@ func runDump(ctx *cli.Context) error { excludes = append(excludes, opts.ProviderConfig) } - if ctx.IsSet("skip-index") && ctx.Bool("skip-index") { + if cmd.IsSet("skip-index") && cmd.Bool("skip-index") { excludes = append(excludes, setting.Indexer.RepoPath) excludes = append(excludes, setting.Indexer.IssuePath) } @@ -278,7 +279,7 @@ func runDump(ctx *cli.Context) error { } } - if ctx.IsSet("skip-attachment-data") && ctx.Bool("skip-attachment-data") { + if cmd.IsSet("skip-attachment-data") && cmd.Bool("skip-attachment-data") { log.Info("Skip dumping attachment data") } else if err := storage.Attachments.IterateObjects("", func(objPath string, object storage.Object) error { info, err := object.Stat() @@ -290,7 +291,7 @@ func runDump(ctx *cli.Context) error { fatal("Failed to dump attachments: %v", err) } - if ctx.IsSet("skip-package-data") && ctx.Bool("skip-package-data") { + if cmd.IsSet("skip-package-data") && cmd.Bool("skip-package-data") { log.Info("Skip dumping package data") } else if !setting.Packages.Enabled { log.Info("Packages isn't enabled. Skip dumping package data") @@ -307,7 +308,7 @@ func runDump(ctx *cli.Context) error { // Doesn't check if LogRootPath exists before processing --skip-log intentionally, // ensuring that it's clear the dump is skipped whether the directory's initialized // yet or not. - if ctx.IsSet("skip-log") && ctx.Bool("skip-log") { + if cmd.IsSet("skip-log") && cmd.Bool("skip-log") { log.Info("Skip dumping log files") } else { isExist, err := util.IsExist(setting.Log.RootPath) diff --git a/cmd/dump_repo.go b/cmd/dump_repo.go index 3a24cf6c5f029..4b125e5f695d5 100644 --- a/cmd/dump_repo.go +++ b/cmd/dump_repo.go @@ -19,7 +19,7 @@ import ( "code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/migrations" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdDumpRepository represents the available dump repository sub-command. @@ -79,7 +79,7 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme }, } -func runDumpRepository(ctx *cli.Context) error { +func runDumpRepository(_ context.Context, cmd *cli.Command) error { stdCtx, cancel := installSignals() defer cancel() @@ -100,8 +100,8 @@ func runDumpRepository(ctx *cli.Context) error { var ( serviceType structs.GitServiceType - cloneAddr = ctx.String("clone_addr") - serviceStr = ctx.String("git_service") + cloneAddr = cmd.String("clone_addr") + serviceStr = cmd.String("git_service") ) if strings.HasPrefix(strings.ToLower(cloneAddr), "https://github.com/") { @@ -119,13 +119,13 @@ func runDumpRepository(ctx *cli.Context) error { opts := base.MigrateOptions{ GitServiceType: serviceType, CloneAddr: cloneAddr, - AuthUsername: ctx.String("auth_username"), - AuthPassword: ctx.String("auth_password"), - AuthToken: ctx.String("auth_token"), - RepoName: ctx.String("repo_name"), + AuthUsername: cmd.String("auth_username"), + AuthPassword: cmd.String("auth_password"), + AuthToken: cmd.String("auth_token"), + RepoName: cmd.String("repo_name"), } - if len(ctx.String("units")) == 0 { + if len(cmd.String("units")) == 0 { opts.Wiki = true opts.Issues = true opts.Milestones = true @@ -135,7 +135,7 @@ func runDumpRepository(ctx *cli.Context) error { opts.PullRequests = true opts.ReleaseAssets = true } else { - units := strings.Split(ctx.String("units"), ",") + units := strings.Split(cmd.String("units"), ",") for _, unit := range units { switch strings.ToLower(strings.TrimSpace(unit)) { case "": @@ -164,7 +164,7 @@ func runDumpRepository(ctx *cli.Context) error { // the repo_dir will be removed if error occurs in DumpRepository // make sure the directory doesn't exist or is empty, prevent from deleting user files - repoDir := ctx.String("repo_dir") + repoDir := cmd.String("repo_dir") if exists, err := util.IsExist(repoDir); err != nil { return fmt.Errorf("unable to stat repo_dir %q: %w", repoDir, err) } else if exists { @@ -179,7 +179,7 @@ func runDumpRepository(ctx *cli.Context) error { if err := migrations.DumpRepository( context.Background(), repoDir, - ctx.String("owner_name"), + cmd.String("owner_name"), opts, ); err != nil { log.Fatal("Failed to dump repository: %v", err) diff --git a/cmd/embedded.go b/cmd/embedded.go index 9f03f7be7c416..086bc06863aa9 100644 --- a/cmd/embedded.go +++ b/cmd/embedded.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "os" @@ -19,7 +20,7 @@ import ( "code.gitea.io/gitea/modules/util" "github.com/gobwas/glob" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdEmbedded represents the available extract sub-command. @@ -28,7 +29,7 @@ var ( Name: "embedded", Usage: "Extract embedded resources", Description: "A command for extracting embedded resources, like templates and images", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ subcmdList, subcmdView, subcmdExtract, @@ -100,7 +101,7 @@ type assetFile struct { path string } -func initEmbeddedExtractor(c *cli.Context) error { +func initEmbeddedExtractor(c *cli.Command) error { setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr) patterns, err := compileCollectPatterns(c.Args().Slice()) @@ -115,7 +116,7 @@ func initEmbeddedExtractor(c *cli.Context) error { return nil } -func runList(c *cli.Context) error { +func runList(_ context.Context, c *cli.Command) error { if err := runListDo(c); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) return err @@ -123,7 +124,7 @@ func runList(c *cli.Context) error { return nil } -func runView(c *cli.Context) error { +func runView(_ context.Context, c *cli.Command) error { if err := runViewDo(c); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) return err @@ -131,7 +132,7 @@ func runView(c *cli.Context) error { return nil } -func runExtract(c *cli.Context) error { +func runExtract(_ context.Context, c *cli.Command) error { if err := runExtractDo(c); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) return err @@ -139,7 +140,7 @@ func runExtract(c *cli.Context) error { return nil } -func runListDo(c *cli.Context) error { +func runListDo(c *cli.Command) error { if err := initEmbeddedExtractor(c); err != nil { return err } @@ -151,7 +152,7 @@ func runListDo(c *cli.Context) error { return nil } -func runViewDo(c *cli.Context) error { +func runViewDo(c *cli.Command) error { if err := initEmbeddedExtractor(c); err != nil { return err } @@ -174,7 +175,7 @@ func runViewDo(c *cli.Context) error { return nil } -func runExtractDo(c *cli.Context) error { +func runExtractDo(c *cli.Command) error { if err := initEmbeddedExtractor(c); err != nil { return err } @@ -271,7 +272,7 @@ func extractAsset(d string, a assetFile, overwrite, rename bool) error { return nil } -func collectAssetFilesByPattern(c *cli.Context, globs []glob.Glob, path string, layer *assetfs.Layer) { +func collectAssetFilesByPattern(c *cli.Command, globs []glob.Glob, path string, layer *assetfs.Layer) { fs := assetfs.Layered(layer) files, err := fs.ListAllFiles(".", true) if err != nil { diff --git a/cmd/generate.go b/cmd/generate.go index 90b32ecaf0e1c..cf491604efac9 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -5,13 +5,14 @@ package cmd import ( + "context" "fmt" "os" "code.gitea.io/gitea/modules/generate" "github.com/mattn/go-isatty" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( @@ -19,7 +20,7 @@ var ( CmdGenerate = &cli.Command{ Name: "generate", Usage: "Generate Gitea's secrets/keys/tokens", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ subcmdSecret, }, } @@ -27,7 +28,7 @@ var ( subcmdSecret = &cli.Command{ Name: "secret", Usage: "Generate a secret token", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ microcmdGenerateInternalToken, microcmdGenerateLfsJwtSecret, microcmdGenerateSecretKey, @@ -54,7 +55,7 @@ var ( } ) -func runGenerateInternalToken(c *cli.Context) error { +func runGenerateInternalToken(_ context.Context, c *cli.Command) error { internalToken, err := generate.NewInternalToken() if err != nil { return err @@ -69,7 +70,7 @@ func runGenerateInternalToken(c *cli.Context) error { return nil } -func runGenerateLfsJwtSecret(c *cli.Context) error { +func runGenerateLfsJwtSecret(_ context.Context, c *cli.Command) error { _, jwtSecretBase64, err := generate.NewJwtSecretWithBase64() if err != nil { return err @@ -84,7 +85,7 @@ func runGenerateLfsJwtSecret(c *cli.Context) error { return nil } -func runGenerateSecretKey(c *cli.Context) error { +func runGenerateSecretKey(_ context.Context, c *cli.Command) error { secretKey, err := generate.NewSecretKey() if err != nil { return err diff --git a/cmd/hook.go b/cmd/hook.go index 41e3c3ce340f3..7a9f8c84a45b4 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -20,7 +20,7 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) const ( @@ -34,7 +34,7 @@ var ( Usage: "(internal) Should only be called by Git", Description: "Delegate commands to corresponding Git hooks", Before: PrepareConsoleLoggerLevel(log.FATAL), - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ subcmdHookPreReceive, subcmdHookUpdate, subcmdHookPostReceive, @@ -161,7 +161,7 @@ func (n *nilWriter) WriteString(s string) (int, error) { return len(s), nil } -func runHookPreReceive(c *cli.Context) error { +func runHookPreReceive(_ context.Context, c *cli.Command) error { if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { return nil } @@ -292,7 +292,7 @@ Gitea or set your environment appropriately.`, "") // runHookUpdate avoid to do heavy operations on update hook because it will be // invoked for every ref update which does not like pre-receive and post-receive -func runHookUpdate(c *cli.Context) error { +func runHookUpdate(_ context.Context, c *cli.Command) error { if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { return nil } @@ -309,7 +309,7 @@ func runHookUpdate(c *cli.Context) error { return nil } -func runHookPostReceive(c *cli.Context) error { +func runHookPostReceive(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() @@ -496,7 +496,7 @@ func pushOptions() map[string]string { return opts } -func runHookProcReceive(c *cli.Context) error { +func runHookProcReceive(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() diff --git a/cmd/keys.go b/cmd/keys.go index 7fdbe16119f5e..a63025be6b482 100644 --- a/cmd/keys.go +++ b/cmd/keys.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "strings" @@ -11,7 +12,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdKeys represents the available keys sub-command @@ -49,7 +50,7 @@ var CmdKeys = &cli.Command{ }, } -func runKeys(c *cli.Context) error { +func runKeys(_ context.Context, c *cli.Command) error { if !c.IsSet("username") { return errors.New("No username provided") } @@ -78,6 +79,6 @@ func runKeys(c *cli.Context) error { if extra.Error != nil { return extra.Error } - _, _ = fmt.Fprintln(c.App.Writer, strings.TrimSpace(authorizedString.Text)) + _, _ = fmt.Fprintln(c.Writer, strings.TrimSpace(authorizedString.Text)) return nil } diff --git a/cmd/mailer.go b/cmd/mailer.go index 0c5f2c8c8d472..7721c8db914f6 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -4,15 +4,16 @@ package cmd import ( + "context" "fmt" "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) -func runSendMail(c *cli.Context) error { +func runSendMail(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() diff --git a/cmd/main.go b/cmd/main.go index 7251bd09a3fe3..d5bbd9cac85d3 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "fmt" "os" "strings" @@ -11,7 +12,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // cmdHelp is our own help subcommand with more information @@ -22,18 +23,18 @@ func cmdHelp() *cli.Command { Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", - Action: func(c *cli.Context) (err error) { + Action: func(_ context.Context, c *cli.Command) (err error) { lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea, {Command:nil} targetCmdIdx := 0 - if c.Command.Name == "help" { + if c.Name == "help" { targetCmdIdx = 1 } - if lineage[targetCmdIdx+1].Command != nil { - err = cli.ShowCommandHelp(lineage[targetCmdIdx+1], lineage[targetCmdIdx].Command.Name) + if lineage[targetCmdIdx+1] != nil { + err = cli.ShowCommandHelp(context.Background(), lineage[targetCmdIdx+1], lineage[targetCmdIdx].Name) } else { err = cli.ShowAppHelp(c) } - _, _ = fmt.Fprintf(c.App.Writer, ` + _, _ = fmt.Fprintf(c.Writer, ` DEFAULT CONFIGURATION: AppPath: %s WorkPath: %s @@ -79,20 +80,20 @@ func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) { command.Action = prepareWorkPathAndCustomConf(command.Action) command.HideHelp = true if command.Name != "help" { - command.Subcommands = append(command.Subcommands, cmdHelp()) + command.Commands = append(command.Commands, cmdHelp()) } - for i := range command.Subcommands { - prepareSubcommandWithConfig(command.Subcommands[i], globalFlags) + for i := range command.Commands { + prepareSubcommandWithConfig(command.Commands[i], globalFlags) } } // prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config // It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times -func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) error { - return func(ctx *cli.Context) error { +func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(context.Context, *cli.Command) error { + return func(ctx context.Context, cmd *cli.Command) error { var args setting.ArgWorkPathAndCustomConf // from children to parent, check the global flags - for _, curCtx := range ctx.Lineage() { + for _, curCtx := range cmd.Lineage() { if curCtx.IsSet("work-path") && args.WorkPath == "" { args.WorkPath = curCtx.String("work-path") } @@ -104,11 +105,11 @@ func prepareWorkPathAndCustomConf(action cli.ActionFunc) func(ctx *cli.Context) } } setting.InitWorkPathAndCommonConfig(os.Getenv, args) - if ctx.Bool("help") || action == nil { + if cmd.Bool("help") || action == nil { // the default behavior of "urfave/cli": "nil action" means "show help" - return cmdHelp().Action(ctx) + return cmdHelp().Action(ctx, cmd) } - return action(ctx) + return action(ctx, cmd) } } @@ -117,14 +118,16 @@ type AppVersion struct { Extra string } -func NewMainApp(appVer AppVersion) *cli.App { - app := cli.NewApp() - app.Name = "Gitea" - app.HelpName = "gitea" - app.Usage = "A painless self-hosted Git service" - app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.` - app.Version = appVer.Version + appVer.Extra - app.EnableBashCompletion = true +func NewMainApp(appVer AppVersion) *cli.Command { + app := &cli.Command{ + Name: "Gitea", + // HelpName: "gitea", + Usage: "A painless self-hosted Git service", + Description: `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.`, + Version: appVer.Version + appVer.Extra, + EnableShellCompletion: true, + } + app.FullName() // these sub-commands need to use config file subCmdWithConfig := []*cli.Command{ @@ -169,8 +172,8 @@ func NewMainApp(appVer AppVersion) *cli.App { return app } -func RunMainApp(app *cli.App, args ...string) error { - err := app.Run(args) +func RunMainApp(app *cli.Command, args ...string) error { + err := app.Run(context.Background(), args) if err == nil { return nil } diff --git a/cmd/main_test.go b/cmd/main_test.go index 9573cacbd4d20..2d27eba98947a 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "io" @@ -16,7 +17,7 @@ import ( "code.gitea.io/gitea/modules/test" "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func TestMain(m *testing.M) { @@ -27,7 +28,7 @@ func makePathOutput(workPath, customPath, customConf string) string { return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf) } -func newTestApp(testCmdAction func(ctx *cli.Context) error) *cli.App { +func newTestApp(testCmdAction func(ctx context.Context, cmd *cli.Command) error) *cli.Command { app := NewMainApp(AppVersion{}) testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction} prepareSubcommandWithConfig(testCmd, appGlobalFlags()) @@ -42,7 +43,7 @@ type runResult struct { ExitCode int } -func runTestApp(app *cli.App, args ...string) (runResult, error) { +func runTestApp(app *cli.Command, args ...string) (runResult, error) { outBuf := new(strings.Builder) errBuf := new(strings.Builder) app.Writer = outBuf @@ -65,7 +66,7 @@ func TestCliCmd(t *testing.T) { defaultCustomConf := filepath.Join(defaultCustomPath, "conf/app.ini") cli.CommandHelpTemplate = "(command help template)" - cli.AppHelpTemplate = "(app help template)" + cli.RootCommandHelpTemplate = "(app help template)" cli.SubcommandHelpTemplate = "(subcommand help template)" cases := []struct { @@ -109,8 +110,8 @@ func TestCliCmd(t *testing.T) { }, } - app := newTestApp(func(ctx *cli.Context) error { - _, _ = fmt.Fprint(ctx.App.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) + app := newTestApp(func(ctx context.Context, cmd *cli.Command) error { + _, _ = fmt.Fprint(cmd.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) return nil }) for _, c := range cases { @@ -128,28 +129,28 @@ func TestCliCmd(t *testing.T) { } func TestCliCmdError(t *testing.T) { - app := newTestApp(func(ctx *cli.Context) error { return errors.New("normal error") }) + app := newTestApp(func(ctx context.Context, cmd *cli.Command) error { return errors.New("normal error") }) r, err := runTestApp(app, "./gitea", "test-cmd") assert.Error(t, err) assert.Equal(t, 1, r.ExitCode) assert.Empty(t, r.Stdout) assert.Equal(t, "Command error: normal error\n", r.Stderr) - app = newTestApp(func(ctx *cli.Context) error { return cli.Exit("exit error", 2) }) + app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return cli.Exit("exit error", 2) }) r, err = runTestApp(app, "./gitea", "test-cmd") assert.Error(t, err) assert.Equal(t, 2, r.ExitCode) assert.Empty(t, r.Stdout) assert.Equal(t, "exit error\n", r.Stderr) - app = newTestApp(func(ctx *cli.Context) error { return nil }) + app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return nil }) r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such") assert.Error(t, err) assert.Equal(t, 1, r.ExitCode) assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stdout) assert.Empty(t, r.Stderr) // the cli package's strange behavior, the error message is not in stderr .... - app = newTestApp(func(ctx *cli.Context) error { return nil }) + app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return nil }) r, err = runTestApp(app, "./gitea", "test-cmd") assert.NoError(t, err) assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called diff --git a/cmd/manager.go b/cmd/manager.go index bd2da8edc7826..c4b07e4f1cc41 100644 --- a/cmd/manager.go +++ b/cmd/manager.go @@ -4,12 +4,13 @@ package cmd import ( + "context" "os" "time" "code.gitea.io/gitea/modules/private" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( @@ -18,7 +19,7 @@ var ( Name: "manager", Usage: "Manage the running gitea process", Description: "This is a command for managing the running gitea process", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ subcmdShutdown, subcmdRestart, subcmdReloadTemplates, @@ -108,7 +109,7 @@ var ( } ) -func runShutdown(c *cli.Context) error { +func runShutdown(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() @@ -117,7 +118,7 @@ func runShutdown(c *cli.Context) error { return handleCliResponseExtra(extra) } -func runRestart(c *cli.Context) error { +func runRestart(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() @@ -126,7 +127,7 @@ func runRestart(c *cli.Context) error { return handleCliResponseExtra(extra) } -func runReloadTemplates(c *cli.Context) error { +func runReloadTemplates(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() @@ -135,7 +136,7 @@ func runReloadTemplates(c *cli.Context) error { return handleCliResponseExtra(extra) } -func runFlushQueues(c *cli.Context) error { +func runFlushQueues(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() @@ -144,7 +145,7 @@ func runFlushQueues(c *cli.Context) error { return handleCliResponseExtra(extra) } -func runProcesses(c *cli.Context) error { +func runProcesses(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() diff --git a/cmd/manager_logging.go b/cmd/manager_logging.go index c2ae25ec57237..cb54ebb5c4922 100644 --- a/cmd/manager_logging.go +++ b/cmd/manager_logging.go @@ -4,6 +4,7 @@ package cmd import ( + "context" "errors" "fmt" "os" @@ -11,7 +12,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var ( @@ -60,7 +61,7 @@ var ( subcmdLogging = &cli.Command{ Name: "logging", Usage: "Adjust logging commands", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ { Name: "pause", Usage: "Pause logging (Gitea will buffer logs up to a certain point and will drop them after that point)", @@ -104,7 +105,7 @@ var ( }, { Name: "add", Usage: "Add a logger", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ { Name: "file", Usage: "Add a file logger", @@ -195,7 +196,7 @@ var ( } ) -func runRemoveLogger(c *cli.Context) error { +func runRemoveLogger(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() @@ -210,7 +211,7 @@ func runRemoveLogger(c *cli.Context) error { return handleCliResponseExtra(extra) } -func runAddConnLogger(c *cli.Context) error { +func runAddConnLogger(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() @@ -240,7 +241,7 @@ func runAddConnLogger(c *cli.Context) error { return commonAddLogger(c, mode, vals) } -func runAddFileLogger(c *cli.Context) error { +func runAddFileLogger(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() @@ -273,7 +274,7 @@ func runAddFileLogger(c *cli.Context) error { return commonAddLogger(c, mode, vals) } -func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error { +func commonAddLogger(c *cli.Command, mode string, vals map[string]any) error { if len(c.String("level")) > 0 { vals["level"] = log.LevelFromString(c.String("level")).String() } @@ -307,7 +308,7 @@ func commonAddLogger(c *cli.Context, mode string, vals map[string]any) error { return handleCliResponseExtra(extra) } -func runPauseLogging(c *cli.Context) error { +func runPauseLogging(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() @@ -317,7 +318,7 @@ func runPauseLogging(c *cli.Context) error { return nil } -func runResumeLogging(c *cli.Context) error { +func runResumeLogging(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() @@ -327,7 +328,7 @@ func runResumeLogging(c *cli.Context) error { return nil } -func runReleaseReopenLogging(c *cli.Context) error { +func runReleaseReopenLogging(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() @@ -337,7 +338,7 @@ func runReleaseReopenLogging(c *cli.Context) error { return nil } -func runSetLogSQL(c *cli.Context) error { +func runSetLogSQL(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() setup(ctx, c.Bool("debug")) diff --git a/cmd/migrate.go b/cmd/migrate.go index 25d8b50c45c61..8b7653a980fc5 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -11,7 +11,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/versioned_migration" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdMigrate represents the available migrate sub-command. @@ -22,7 +22,7 @@ var CmdMigrate = &cli.Command{ Action: runMigrate, } -func runMigrate(ctx *cli.Context) error { +func runMigrate(_ context.Context, c *cli.Command) error { stdCtx, cancel := installSignals() defer cancel() diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go index f9ed140395f60..af83aee80cb0a 100644 --- a/cmd/migrate_storage.go +++ b/cmd/migrate_storage.go @@ -22,7 +22,7 @@ import ( "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/services/versioned_migration" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdMigrateStorage represents the available migrate storage sub-command. @@ -213,7 +213,7 @@ func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStora }) } -func runMigrateStorage(ctx *cli.Context) error { +func runMigrateStorage(_ context.Context, cmd *cli.Command) error { stdCtx, cancel := installSignals() defer cancel() @@ -238,11 +238,11 @@ func runMigrateStorage(ctx *cli.Context) error { var dstStorage storage.ObjectStorage var err error - switch strings.ToLower(ctx.String("storage")) { + switch strings.ToLower(cmd.String("storage")) { case "": fallthrough case string(setting.LocalStorageType): - p := ctx.String("path") + p := cmd.String("path") if p == "" { log.Fatal("Path must be given when storage is local") return nil @@ -257,16 +257,16 @@ func runMigrateStorage(ctx *cli.Context) error { stdCtx, &setting.Storage{ MinioConfig: setting.MinioStorageConfig{ - Endpoint: ctx.String("minio-endpoint"), - AccessKeyID: ctx.String("minio-access-key-id"), - SecretAccessKey: ctx.String("minio-secret-access-key"), - Bucket: ctx.String("minio-bucket"), - Location: ctx.String("minio-location"), - BasePath: ctx.String("minio-base-path"), - UseSSL: ctx.Bool("minio-use-ssl"), - InsecureSkipVerify: ctx.Bool("minio-insecure-skip-verify"), - ChecksumAlgorithm: ctx.String("minio-checksum-algorithm"), - BucketLookUpType: ctx.String("minio-bucket-lookup-type"), + Endpoint: cmd.String("minio-endpoint"), + AccessKeyID: cmd.String("minio-access-key-id"), + SecretAccessKey: cmd.String("minio-secret-access-key"), + Bucket: cmd.String("minio-bucket"), + Location: cmd.String("minio-location"), + BasePath: cmd.String("minio-base-path"), + UseSSL: cmd.Bool("minio-use-ssl"), + InsecureSkipVerify: cmd.Bool("minio-insecure-skip-verify"), + ChecksumAlgorithm: cmd.String("minio-checksum-algorithm"), + BucketLookUpType: cmd.String("minio-bucket-lookup-type"), }, }) case string(setting.AzureBlobStorageType): @@ -274,15 +274,15 @@ func runMigrateStorage(ctx *cli.Context) error { stdCtx, &setting.Storage{ AzureBlobConfig: setting.AzureBlobStorageConfig{ - Endpoint: ctx.String("azureblob-endpoint"), - AccountName: ctx.String("azureblob-account-name"), - AccountKey: ctx.String("azureblob-account-key"), - Container: ctx.String("azureblob-container"), - BasePath: ctx.String("azureblob-base-path"), + Endpoint: cmd.String("azureblob-endpoint"), + AccountName: cmd.String("azureblob-account-name"), + AccountKey: cmd.String("azureblob-account-key"), + Container: cmd.String("azureblob-container"), + BasePath: cmd.String("azureblob-base-path"), }, }) default: - return fmt.Errorf("unsupported storage type: %s", ctx.String("storage")) + return fmt.Errorf("unsupported storage type: %s", cmd.String("storage")) } if err != nil { return err @@ -299,7 +299,7 @@ func runMigrateStorage(ctx *cli.Context) error { "actions-artifacts": migrateActionsArtifacts, } - tp := strings.ToLower(ctx.String("type")) + tp := strings.ToLower(cmd.String("type")) if m, ok := migratedMethods[tp]; ok { if err := m(stdCtx, dstStorage); err != nil { return err @@ -308,5 +308,5 @@ func runMigrateStorage(ctx *cli.Context) error { return nil } - return fmt.Errorf("unsupported storage: %s", ctx.String("type")) + return fmt.Errorf("unsupported storage: %s", cmd.String("type")) } diff --git a/cmd/restore_repo.go b/cmd/restore_repo.go index 37b32aa3045da..238789615fddb 100644 --- a/cmd/restore_repo.go +++ b/cmd/restore_repo.go @@ -4,12 +4,13 @@ package cmd import ( + "context" "strings" "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdRestoreRepository represents the available restore a repository sub-command. @@ -48,7 +49,7 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme }, } -func runRestoreRepository(c *cli.Context) error { +func runRestoreRepository(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() diff --git a/cmd/serv.go b/cmd/serv.go index 26a3af50f329f..9f5665c66833b 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -33,7 +33,7 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/kballard/go-shellquote" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // CmdServ represents the available serv sub-command. @@ -152,7 +152,7 @@ func getLFSAuthToken(ctx context.Context, lfsVerb string, results *private.ServC return "Bearer " + tokenString, nil } -func runServ(c *cli.Context) error { +func runServ(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() diff --git a/cmd/web.go b/cmd/web.go index e47b171455c44..39e336fe54685 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -28,7 +28,7 @@ import ( "code.gitea.io/gitea/routers/install" "github.com/felixge/fgprof" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // PIDFile could be set from build tag @@ -130,19 +130,19 @@ func showWebStartupMessage(msg string) { } } -func serveInstall(ctx *cli.Context) error { +func serveInstall(cmd *cli.Command) error { showWebStartupMessage("Prepare to run install page") routers.InitWebInstallPage(graceful.GetManager().HammerContext()) // Flag for port number in case first time run conflict - if ctx.IsSet("port") { - if err := setPort(ctx.String("port")); err != nil { + if cmd.IsSet("port") { + if err := setPort(cmd.String("port")); err != nil { return err } } - if ctx.IsSet("install-port") { - if err := setPort(ctx.String("install-port")); err != nil { + if cmd.IsSet("install-port") { + if err := setPort(cmd.String("install-port")); err != nil { return err } } @@ -163,7 +163,7 @@ func serveInstall(ctx *cli.Context) error { return nil } -func serveInstalled(ctx *cli.Context) error { +func serveInstalled(c *cli.Command) error { setting.InitCfgProvider(setting.CustomConf) setting.LoadCommonSettings() setting.MustInstalled() @@ -218,8 +218,8 @@ func serveInstalled(ctx *cli.Context) error { setting.AppDataTempDir("").RemoveOutdated(3 * 24 * time.Hour) // Override the provided port number within the configuration - if ctx.IsSet("port") { - if err := setPort(ctx.String("port")); err != nil { + if c.IsSet("port") { + if err := setPort(c.String("port")); err != nil { return err } } @@ -244,7 +244,7 @@ func servePprof() { finished() } -func runWeb(ctx *cli.Context) error { +func runWeb(_ context.Context, cmd *cli.Command) error { defer func() { if panicked := recover(); panicked != nil { log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2)) @@ -262,12 +262,12 @@ func runWeb(ctx *cli.Context) error { } // Set pid file setting - if ctx.IsSet("pid") { - createPIDFile(ctx.String("pid")) + if cmd.IsSet("pid") { + createPIDFile(cmd.String("pid")) } if !setting.InstallLock { - if err := serveInstall(ctx); err != nil { + if err := serveInstall(cmd); err != nil { return err } } else { @@ -278,7 +278,7 @@ func runWeb(ctx *cli.Context) error { go servePprof() } - return serveInstalled(ctx) + return serveInstalled(cmd) } func setPort(port string) error { diff --git a/contrib/backport/backport.go b/contrib/backport/backport.go index 44e4eacf906ef..e0f21e451d6c8 100644 --- a/contrib/backport/backport.go +++ b/contrib/backport/backport.go @@ -19,14 +19,14 @@ import ( "syscall" "github.com/google/go-github/v71/github" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "gopkg.in/yaml.v3" ) const defaultVersion = "v1.18" // to backport to func main() { - app := cli.NewApp() + app := &cli.Command{} app.Name = "backport" app.Usage = "Backport provided PR-number on to the current or previous released version" app.Description = `Backport will look-up the PR in Gitea's git log and attempt to cherry-pick it on the current version` @@ -91,7 +91,7 @@ func main() { Usage: "Set this flag to continue from a git cherry-pick that has broken", }, } - cli.AppHelpTemplate = `NAME: + cli.RootCommandHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: {{.HelpName}} {{if .VisibleFlags}}[options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} @@ -106,12 +106,12 @@ OPTIONS: app.Action = runBackport - if err := app.Run(os.Args); err != nil { + if err := app.Run(context.Background(), os.Args); err != nil { fmt.Fprintf(os.Stderr, "Unable to backport: %v\n", err) } } -func runBackport(c *cli.Context) error { +func runBackport(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() diff --git a/contrib/environment-to-ini/environment-to-ini.go b/contrib/environment-to-ini/environment-to-ini.go index a7d7a6d293d95..5eb576c6feab7 100644 --- a/contrib/environment-to-ini/environment-to-ini.go +++ b/contrib/environment-to-ini/environment-to-ini.go @@ -4,16 +4,17 @@ package main import ( + "context" "os" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func main() { - app := cli.NewApp() + app := cli.Command{} app.Name = "environment-to-ini" app.Usage = "Use provided environment to update configuration ini" app.Description = `As a helper to allow docker users to update the gitea configuration @@ -72,13 +73,13 @@ func main() { }, } app.Action = runEnvironmentToIni - err := app.Run(os.Args) + err := app.Run(context.Background(), os.Args) if err != nil { log.Fatal("Failed to run app with %s: %v", os.Args, err) } } -func runEnvironmentToIni(c *cli.Context) error { +func runEnvironmentToIni(_ context.Context, c *cli.Command) error { // the config system may change the environment variables, so get a copy first, to be used later env := append([]string{}, os.Environ()...) setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{ diff --git a/go.mod b/go.mod index a43a0a31114cb..d47d3b5d568e1 100644 --- a/go.mod +++ b/go.mod @@ -110,7 +110,8 @@ require ( github.com/syndtr/goleveldb v1.0.0 github.com/tstranex/u2f v1.0.0 github.com/ulikunitz/xz v0.5.12 - github.com/urfave/cli/v2 v2.27.6 + github.com/urfave/cli-docs/v3 v3.0.0-alpha6 + github.com/urfave/cli/v3 v3.3.3 github.com/wneessen/go-mail v0.6.2 github.com/xeipuuv/gojsonschema v1.2.0 github.com/yohcop/openid-go v1.0.1 @@ -186,7 +187,7 @@ require ( github.com/couchbase/go-couchbase v0.1.1 // indirect github.com/couchbase/gomemcached v0.3.3 // indirect github.com/couchbase/goutils v0.1.2 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidmz/go-pageant v1.0.2 // indirect @@ -296,7 +297,6 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/zeebo/assert v1.3.0 // indirect github.com/zeebo/blake3 v0.2.4 // indirect go.etcd.io/bbolt v1.4.0 // indirect diff --git a/go.sum b/go.sum index 9b200cc2d9477..a2a39d8b95ed4 100644 --- a/go.sum +++ b/go.sum @@ -223,8 +223,8 @@ github.com/couchbase/goutils v0.1.2 h1:gWr8B6XNWPIhfalHNog3qQKfGiYyh4K4VhO3P2o9B github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE= github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= @@ -761,8 +761,10 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= -github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g= -github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/urfave/cli-docs/v3 v3.0.0-alpha6 h1:w/l/N0xw1rO/aHRIGXJ0lDwwYFOzilup1qGvIytP3BI= +github.com/urfave/cli-docs/v3 v3.0.0-alpha6/go.mod h1:p7Z4lg8FSTrPB9GTaNyTrK3ygffHZcK3w0cU2VE+mzU= +github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I= +github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= @@ -782,8 +784,6 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js= diff --git a/main.go b/main.go index 756c3e0f9ba12..2c25bac4e3dd2 100644 --- a/main.go +++ b/main.go @@ -21,7 +21,7 @@ import ( _ "code.gitea.io/gitea/modules/markup/markdown" _ "code.gitea.io/gitea/modules/markup/orgmode" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // these flags will be set by the build flags diff --git a/tests/integration/cmd_keys_test.go b/tests/integration/cmd_keys_test.go index 61f11c58b0cbc..daefed1a543a9 100644 --- a/tests/integration/cmd_keys_test.go +++ b/tests/integration/cmd_keys_test.go @@ -13,7 +13,7 @@ import ( "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) func Test_CmdKeys(t *testing.T) { @@ -37,11 +37,12 @@ func Test_CmdKeys(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { out := new(bytes.Buffer) - app := cli.NewApp() - app.Writer = out - app.Commands = []*cli.Command{cmd.CmdKeys} + app := &cli.Command{ + Writer: out, + Commands: []*cli.Command{cmd.CmdKeys}, + } cmd.CmdKeys.HideHelp = true - err := app.Run(append([]string{"prog"}, tt.args...)) + err := app.Run(t.Context(), append([]string{"prog"}, tt.args...)) if tt.wantErr { assert.Error(t, err) } else { From df9c7f21a598b210015867d9bc9940a321810638 Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Sun, 18 May 2025 20:18:50 +0200 Subject: [PATCH 02/18] inline flags for ldap commands ensure flags don't carry state through tests --- cmd/admin.go | 8 +- cmd/admin_auth_ldap.go | 287 ++++++++++++++++-------------------- cmd/admin_auth_ldap_test.go | 38 ++--- 3 files changed, 148 insertions(+), 185 deletions(-) diff --git a/cmd/admin.go b/cmd/admin.go index 32baa625aaeca..241579618d45c 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -53,10 +53,10 @@ var ( Commands: []*cli.Command{ microcmdAuthAddOauth, microcmdAuthUpdateOauth, - microcmdAuthAddLdapBindDn, - microcmdAuthUpdateLdapBindDn, - microcmdAuthAddLdapSimpleAuth, - microcmdAuthUpdateLdapSimpleAuth, + newMicrocmdAuthAddLdapBindDn(), + newMicrocmdAuthUpdateLdapBindDn(), + newMicrocmdAuthAddLdapSimpleAuth(), + newMicrocmdAuthUpdateLdapSimpleAuth(), microcmdAuthAddSMTP, microcmdAuthUpdateSMTP, microcmdAuthList, diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index 740329435547e..e36c64ba87392 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -24,182 +24,157 @@ type ( } ) -var ( - commonLdapCLIFlags = []cli.Flag{ - &cli.StringFlag{ - Name: "name", - Usage: "Authentication name.", - }, - &cli.BoolFlag{ - Name: "not-active", - Usage: "Deactivate the authentication source.", - }, - &cli.BoolFlag{ - Name: "active", - Usage: "Activate the authentication source.", - }, - &cli.StringFlag{ - Name: "security-protocol", - Usage: "Security protocol name.", - }, - &cli.BoolFlag{ - Name: "skip-tls-verify", - Usage: "Disable TLS verification.", - }, - &cli.StringFlag{ - Name: "host", - Usage: "The address where the LDAP server can be reached.", - }, - &cli.IntFlag{ - Name: "port", - Usage: "The port to use when connecting to the LDAP server.", - }, - &cli.StringFlag{ - Name: "user-search-base", - Usage: "The LDAP base at which user accounts will be searched for.", - }, - &cli.StringFlag{ - Name: "user-filter", - Usage: "An LDAP filter declaring how to find the user record that is attempting to authenticate.", - }, - &cli.StringFlag{ - Name: "admin-filter", - Usage: "An LDAP filter specifying if a user should be given administrator privileges.", - }, - &cli.StringFlag{ - Name: "restricted-filter", - Usage: "An LDAP filter specifying if a user should be given restricted status.", - }, - &cli.BoolFlag{ - Name: "allow-deactivate-all", - Usage: "Allow empty search results to deactivate all users.", - }, - &cli.StringFlag{ - Name: "username-attribute", - Usage: "The attribute of the user’s LDAP record containing the user name.", - }, - &cli.StringFlag{ - Name: "firstname-attribute", - Usage: "The attribute of the user’s LDAP record containing the user’s first name.", - }, - &cli.StringFlag{ - Name: "surname-attribute", - Usage: "The attribute of the user’s LDAP record containing the user’s surname.", - }, - &cli.StringFlag{ - Name: "email-attribute", - Usage: "The attribute of the user’s LDAP record containing the user’s email address.", - }, - &cli.StringFlag{ - Name: "public-ssh-key-attribute", - Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key.", - }, - &cli.BoolFlag{ - Name: "skip-local-2fa", - Usage: "Set to true to skip local 2fa for users authenticated by this source", - }, - &cli.StringFlag{ - Name: "avatar-attribute", - Usage: "The attribute of the user’s LDAP record containing the user’s avatar.", - }, - } - - ldapBindDnCLIFlags = append(commonLdapCLIFlags, - &cli.StringFlag{ - Name: "bind-dn", - Usage: "The DN to bind to the LDAP server with when searching for the user.", - }, - &cli.StringFlag{ - Name: "bind-password", - Usage: "The password for the Bind DN, if any.", - }, - &cli.BoolFlag{ - Name: "attributes-in-bind", - Usage: "Fetch attributes in bind DN context.", - }, - &cli.BoolFlag{ - Name: "synchronize-users", - Usage: "Enable user synchronization.", - }, - &cli.BoolFlag{ - Name: "disable-synchronize-users", - Usage: "Disable user synchronization.", - }, - &cli.UintFlag{ - Name: "page-size", - Usage: "Search page size.", - }, - &cli.BoolFlag{ - Name: "enable-groups", - Usage: "Enable LDAP groups", - }, - &cli.StringFlag{ - Name: "group-search-base-dn", - Usage: "The LDAP base DN at which group accounts will be searched for", - }, - &cli.StringFlag{ - Name: "group-member-attribute", - Usage: "Group attribute containing list of users", - }, - &cli.StringFlag{ - Name: "group-user-attribute", - Usage: "User attribute listed in group", - }, - &cli.StringFlag{ - Name: "group-filter", - Usage: "Verify group membership in LDAP", - }, - &cli.StringFlag{ - Name: "group-team-map", - Usage: "Map LDAP groups to Organization teams", - }, - &cli.BoolFlag{ - Name: "group-team-map-removal", - Usage: "Remove users from synchronized teams if user does not belong to corresponding LDAP group", - }) - - ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags, - &cli.StringFlag{ - Name: "user-dn", - Usage: "The user's DN.", - }) - - microcmdAuthAddLdapBindDn = &cli.Command{ +func newMicrocmdAuthAddLdapBindDn() *cli.Command { + return &cli.Command{ Name: "add-ldap", Usage: "Add new LDAP (via Bind DN) authentication source", Action: func(ctx context.Context, cmd *cli.Command) error { return newAuthService().addLdapBindDn(ctx, cmd) }, - Flags: ldapBindDnCLIFlags, + Flags: []cli.Flag{ + &cli.StringFlag{Name: "name", Usage: "Authentication name.", Required: true}, + &cli.BoolFlag{Name: "not-active", Usage: "Deactivate the authentication source."}, + &cli.BoolFlag{Name: "active", Usage: "Activate the authentication source."}, + &cli.StringFlag{Name: "security-protocol", Usage: "Security protocol name.", Required: true}, + &cli.BoolFlag{Name: "skip-tls-verify", Usage: "Disable TLS verification."}, + &cli.StringFlag{Name: "host", Usage: "The address where the LDAP server can be reached.", Required: true}, + &cli.IntFlag{Name: "port", Usage: "The port to use when connecting to the LDAP server.", Required: true}, + &cli.StringFlag{Name: "user-search-base", Usage: "The LDAP base at which user accounts will be searched for.", Required: true}, + &cli.StringFlag{Name: "user-filter", Usage: "An LDAP filter declaring how to find the user record that is attempting to authenticate.", Required: true}, + &cli.StringFlag{Name: "admin-filter", Usage: "An LDAP filter specifying if a user should be given administrator privileges."}, + &cli.StringFlag{Name: "restricted-filter", Usage: "An LDAP filter specifying if a user should be given restricted status."}, + &cli.BoolFlag{Name: "allow-deactivate-all", Usage: "Allow empty search results to deactivate all users."}, + &cli.StringFlag{Name: "username-attribute", Usage: "The attribute of the user’s LDAP record containing the user name."}, + &cli.StringFlag{Name: "firstname-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s first name."}, + &cli.StringFlag{Name: "surname-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s surname."}, + &cli.StringFlag{Name: "email-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s email address.", Required: true}, + &cli.StringFlag{Name: "public-ssh-key-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key."}, + &cli.BoolFlag{Name: "skip-local-2fa", Usage: "Set to true to skip local 2fa for users authenticated by this source"}, + &cli.StringFlag{Name: "avatar-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s avatar."}, + &cli.StringFlag{Name: "bind-dn", Usage: "The DN to bind to the LDAP server with when searching for the user."}, + &cli.StringFlag{Name: "bind-password", Usage: "The password for the Bind DN, if any."}, + &cli.BoolFlag{Name: "attributes-in-bind", Usage: "Fetch attributes in bind DN context."}, + &cli.BoolFlag{Name: "synchronize-users", Usage: "Enable user synchronization."}, + &cli.BoolFlag{Name: "disable-synchronize-users", Usage: "Disable user synchronization."}, + &cli.UintFlag{Name: "page-size", Usage: "Search page size."}, + &cli.BoolFlag{Name: "enable-groups", Usage: "Enable LDAP groups"}, + &cli.StringFlag{Name: "group-search-base-dn", Usage: "The LDAP base DN at which group accounts will be searched for"}, + &cli.StringFlag{Name: "group-member-attribute", Usage: "Group attribute containing list of users"}, + &cli.StringFlag{Name: "group-user-attribute", Usage: "User attribute listed in group"}, + &cli.StringFlag{Name: "group-filter", Usage: "Verify group membership in LDAP"}, + &cli.StringFlag{Name: "group-team-map", Usage: "Map LDAP groups to Organization teams"}, + &cli.BoolFlag{Name: "group-team-map-removal", Usage: "Remove users from synchronized teams if user does not belong to corresponding LDAP group"}, + }, } +} - microcmdAuthUpdateLdapBindDn = &cli.Command{ +func newMicrocmdAuthUpdateLdapBindDn() *cli.Command { + return &cli.Command{ Name: "update-ldap", Usage: "Update existing LDAP (via Bind DN) authentication source", Action: func(ctx context.Context, cmd *cli.Command) error { return newAuthService().updateLdapBindDn(ctx, cmd) }, - Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...), + Flags: []cli.Flag{ + &cli.Int64Flag{Name: "id", Usage: "ID of authentication source", Required: true}, + &cli.StringFlag{Name: "name", Usage: "Authentication name."}, + &cli.BoolFlag{Name: "not-active", Usage: "Deactivate the authentication source."}, + &cli.BoolFlag{Name: "active", Usage: "Activate the authentication source."}, + &cli.StringFlag{Name: "security-protocol", Usage: "Security protocol name."}, + &cli.BoolFlag{Name: "skip-tls-verify", Usage: "Disable TLS verification."}, + &cli.StringFlag{Name: "host", Usage: "The address where the LDAP server can be reached."}, + &cli.IntFlag{Name: "port", Usage: "The port to use when connecting to the LDAP server."}, + &cli.StringFlag{Name: "user-search-base", Usage: "The LDAP base at which user accounts will be searched for."}, + &cli.StringFlag{Name: "user-filter", Usage: "An LDAP filter declaring how to find the user record that is attempting to authenticate."}, + &cli.StringFlag{Name: "admin-filter", Usage: "An LDAP filter specifying if a user should be given administrator privileges."}, + &cli.StringFlag{Name: "restricted-filter", Usage: "An LDAP filter specifying if a user should be given restricted status."}, + &cli.BoolFlag{Name: "allow-deactivate-all", Usage: "Allow empty search results to deactivate all users."}, + &cli.StringFlag{Name: "username-attribute", Usage: "The attribute of the user’s LDAP record containing the user name."}, + &cli.StringFlag{Name: "firstname-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s first name."}, + &cli.StringFlag{Name: "surname-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s surname."}, + &cli.StringFlag{Name: "email-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s email address."}, + &cli.StringFlag{Name: "public-ssh-key-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key."}, + &cli.BoolFlag{Name: "skip-local-2fa", Usage: "Set to true to skip local 2fa for users authenticated by this source"}, + &cli.StringFlag{Name: "avatar-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s avatar."}, + &cli.StringFlag{Name: "bind-dn", Usage: "The DN to bind to the LDAP server with when searching for the user."}, + &cli.StringFlag{Name: "bind-password", Usage: "The password for the Bind DN, if any."}, + &cli.BoolFlag{Name: "attributes-in-bind", Usage: "Fetch attributes in bind DN context."}, + &cli.BoolFlag{Name: "synchronize-users", Usage: "Enable user synchronization."}, + &cli.BoolFlag{Name: "disable-synchronize-users", Usage: "Disable user synchronization."}, + &cli.UintFlag{Name: "page-size", Usage: "Search page size."}, + &cli.BoolFlag{Name: "enable-groups", Usage: "Enable LDAP groups"}, + &cli.StringFlag{Name: "group-search-base-dn", Usage: "The LDAP base DN at which group accounts will be searched for"}, + &cli.StringFlag{Name: "group-member-attribute", Usage: "Group attribute containing list of users"}, + &cli.StringFlag{Name: "group-user-attribute", Usage: "User attribute listed in group"}, + &cli.StringFlag{Name: "group-filter", Usage: "Verify group membership in LDAP"}, + &cli.StringFlag{Name: "group-team-map", Usage: "Map LDAP groups to Organization teams"}, + &cli.BoolFlag{Name: "group-team-map-removal", Usage: "Remove users from synchronized teams if user does not belong to corresponding LDAP group"}, + }, } +} - microcmdAuthAddLdapSimpleAuth = &cli.Command{ +func newMicrocmdAuthAddLdapSimpleAuth() *cli.Command { + return &cli.Command{ Name: "add-ldap-simple", Usage: "Add new LDAP (simple auth) authentication source", Action: func(ctx context.Context, cmd *cli.Command) error { return newAuthService().addLdapSimpleAuth(ctx, cmd) }, - Flags: ldapSimpleAuthCLIFlags, + Flags: []cli.Flag{ + &cli.StringFlag{Name: "name", Usage: "Authentication name.", Required: true}, + &cli.BoolFlag{Name: "not-active", Usage: "Deactivate the authentication source."}, + &cli.BoolFlag{Name: "active", Usage: "Activate the authentication source."}, + &cli.StringFlag{Name: "security-protocol", Usage: "Security protocol name.", Required: true}, + &cli.BoolFlag{Name: "skip-tls-verify", Usage: "Disable TLS verification."}, + &cli.StringFlag{Name: "host", Usage: "The address where the LDAP server can be reached.", Required: true}, + &cli.IntFlag{Name: "port", Usage: "The port to use when connecting to the LDAP server.", Required: true}, + &cli.StringFlag{Name: "user-search-base", Usage: "The LDAP base at which user accounts will be searched for."}, + &cli.StringFlag{Name: "user-filter", Usage: "An LDAP filter declaring how to find the user record that is attempting to authenticate.", Required: true}, + &cli.StringFlag{Name: "admin-filter", Usage: "An LDAP filter specifying if a user should be given administrator privileges."}, + &cli.StringFlag{Name: "restricted-filter", Usage: "An LDAP filter specifying if a user should be given restricted status."}, + &cli.BoolFlag{Name: "allow-deactivate-all", Usage: "Allow empty search results to deactivate all users."}, + &cli.StringFlag{Name: "username-attribute", Usage: "The attribute of the user’s LDAP record containing the user name."}, + &cli.StringFlag{Name: "firstname-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s first name."}, + &cli.StringFlag{Name: "surname-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s surname."}, + &cli.StringFlag{Name: "email-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s email address.", Required: true}, + &cli.StringFlag{Name: "public-ssh-key-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key."}, + &cli.BoolFlag{Name: "skip-local-2fa", Usage: "Set to true to skip local 2fa for users authenticated by this source"}, + &cli.StringFlag{Name: "avatar-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s avatar."}, + &cli.StringFlag{Name: "user-dn", Usage: "The user's DN.", Required: true}}, } +} - microcmdAuthUpdateLdapSimpleAuth = &cli.Command{ +func newMicrocmdAuthUpdateLdapSimpleAuth() *cli.Command { + return &cli.Command{ Name: "update-ldap-simple", Usage: "Update existing LDAP (simple auth) authentication source", Action: func(ctx context.Context, cmd *cli.Command) error { return newAuthService().updateLdapSimpleAuth(ctx, cmd) }, - Flags: append([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...), - } -) + Flags: []cli.Flag{ + &cli.Int64Flag{Name: "id", Usage: "ID of authentication source", Required: true}, + &cli.StringFlag{Name: "name", Usage: "Authentication name."}, + &cli.BoolFlag{Name: "not-active", Usage: "Deactivate the authentication source."}, + &cli.BoolFlag{Name: "active", Usage: "Activate the authentication source."}, + &cli.StringFlag{Name: "security-protocol", Usage: "Security protocol name."}, + &cli.BoolFlag{Name: "skip-tls-verify", Usage: "Disable TLS verification."}, + &cli.StringFlag{Name: "host", Usage: "The address where the LDAP server can be reached."}, + &cli.IntFlag{Name: "port", Usage: "The port to use when connecting to the LDAP server."}, + &cli.StringFlag{Name: "user-search-base", Usage: "The LDAP base at which user accounts will be searched for."}, + &cli.StringFlag{Name: "user-filter", Usage: "An LDAP filter declaring how to find the user record that is attempting to authenticate."}, + &cli.StringFlag{Name: "admin-filter", Usage: "An LDAP filter specifying if a user should be given administrator privileges."}, + &cli.StringFlag{Name: "restricted-filter", Usage: "An LDAP filter specifying if a user should be given restricted status."}, + &cli.BoolFlag{Name: "allow-deactivate-all", Usage: "Allow empty search results to deactivate all users."}, + &cli.StringFlag{Name: "username-attribute", Usage: "The attribute of the user’s LDAP record containing the user name."}, + &cli.StringFlag{Name: "firstname-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s first name."}, + &cli.StringFlag{Name: "surname-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s surname."}, + &cli.StringFlag{Name: "email-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s email address."}, + &cli.StringFlag{Name: "public-ssh-key-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key."}, + &cli.BoolFlag{Name: "skip-local-2fa", Usage: "Set to true to skip local 2fa for users authenticated by this source"}, + &cli.StringFlag{Name: "avatar-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s avatar."}, + &cli.StringFlag{Name: "user-dn", Usage: "The user's DN."}, + }} +} // newAuthService creates a service with default functions. func newAuthService() *authService { @@ -338,10 +313,6 @@ func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) { // getAuthSource gets the login source by its id defined in the command line flags. // It returns an error if the id is not set, does not match any source or if the source is not of expected type. func (a *authService) getAuthSource(ctx context.Context, c *cli.Command, authType auth.Type) (*auth.Source, error) { - if err := argsSet(c, "id"); err != nil { - return nil, err - } - authSource, err := a.getAuthSourceByID(ctx, c.Int64("id")) if err != nil { return nil, err @@ -356,10 +327,6 @@ func (a *authService) getAuthSource(ctx context.Context, c *cli.Command, authTyp // addLdapBindDn adds a new LDAP via Bind DN authentication source. func (a *authService) addLdapBindDn(_ context.Context, c *cli.Command) error { - if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil { - return err - } - ctx, cancel := installSignals() defer cancel() @@ -407,10 +374,6 @@ func (a *authService) updateLdapBindDn(_ context.Context, c *cli.Command) error // addLdapSimpleAuth adds a new LDAP (simple auth) authentication source. func (a *authService) addLdapSimpleAuth(_ context.Context, c *cli.Command) error { - if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil { - return err - } - ctx, cancel := installSignals() defer cancel() diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go index 49061afdabb1a..68a1f9b486389 100644 --- a/cmd/admin_auth_ldap_test.go +++ b/cmd/admin_auth_ldap_test.go @@ -148,7 +148,7 @@ func TestAddLdapBindDn(t *testing.T) { "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", "--email-attribute", "mail", }, - errMsg: "name is not set", + errMsg: "Required flag \"name\" not set", }, // case 4 { @@ -161,7 +161,7 @@ func TestAddLdapBindDn(t *testing.T) { "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", "--email-attribute", "mail", }, - errMsg: "security-protocol is not set", + errMsg: "Required flag \"security-protocol\" not set", }, // case 5 { @@ -174,7 +174,7 @@ func TestAddLdapBindDn(t *testing.T) { "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", "--email-attribute", "mail", }, - errMsg: "host is not set", + errMsg: "Required flag \"host\" not set", }, // case 6 { @@ -187,7 +187,7 @@ func TestAddLdapBindDn(t *testing.T) { "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", "--email-attribute", "mail", }, - errMsg: "port is not set", + errMsg: "Required flag \"port\" not set", }, // case 7 { @@ -200,7 +200,7 @@ func TestAddLdapBindDn(t *testing.T) { "--user-search-base", "ou=Users,dc=domain,dc=org", "--email-attribute", "mail", }, - errMsg: "user-filter is not set", + errMsg: "Required flag \"user-filter\" not set", }, // case 8 { @@ -213,7 +213,7 @@ func TestAddLdapBindDn(t *testing.T) { "--user-search-base", "ou=Users,dc=domain,dc=org", "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", }, - errMsg: "email-attribute is not set", + errMsg: "Required flag \"email-attribute\" not set", }, } @@ -240,7 +240,7 @@ func TestAddLdapBindDn(t *testing.T) { // Create a copy of command to test app := cli.Command{ - Flags: microcmdAuthAddLdapBindDn.Flags, + Flags: newMicrocmdAuthAddLdapBindDn().Flags, Action: service.addLdapBindDn, } @@ -367,7 +367,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { "--email-attribute", "mail", "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", }, - errMsg: "name is not set", + errMsg: "Required flag \"name\" not set", }, // case 4 { @@ -380,7 +380,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { "--email-attribute", "mail", "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", }, - errMsg: "security-protocol is not set", + errMsg: "Required flag \"security-protocol\" not set", }, // case 5 { @@ -393,7 +393,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { "--email-attribute", "mail", "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", }, - errMsg: "host is not set", + errMsg: "Required flag \"host\" not set", }, // case 6 { @@ -406,7 +406,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { "--email-attribute", "mail", "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", }, - errMsg: "port is not set", + errMsg: "Required flag \"port\" not set", }, // case 7 { @@ -419,7 +419,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { "--email-attribute", "mail", "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", }, - errMsg: "user-filter is not set", + errMsg: "Required flag \"user-filter\" not set", }, // case 8 { @@ -432,7 +432,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", }, - errMsg: "email-attribute is not set", + errMsg: "Required flag \"email-attribute\" not set", }, // case 9 { @@ -445,7 +445,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", "--email-attribute", "mail", }, - errMsg: "user-dn is not set", + errMsg: "Required flag \"user-dn\" not set", }, } @@ -472,7 +472,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { // Create a copy of command to test app := &cli.Command{ - Flags: microcmdAuthAddLdapSimpleAuth.Flags, + Flags: newMicrocmdAuthAddLdapSimpleAuth().Flags, Action: service.addLdapSimpleAuth, } @@ -873,7 +873,7 @@ func TestUpdateLdapBindDn(t *testing.T) { args: []string{ "ldap-test", }, - errMsg: "id is not set", + errMsg: "Required flag \"id\" not set", }, // case 23 { @@ -950,7 +950,7 @@ func TestUpdateLdapBindDn(t *testing.T) { // Create a copy of command to test app := cli.Command{ - Flags: microcmdAuthUpdateLdapBindDn.Flags, + Flags: newMicrocmdAuthUpdateLdapBindDn().Flags, Action: service.updateLdapBindDn, } // Run it @@ -1266,7 +1266,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { args: []string{ "ldap-test", }, - errMsg: "id is not set", + errMsg: "Required flag \"id\" not set", }, // case 19 { @@ -1340,7 +1340,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { // Create a copy of command to test app := cli.Command{ - Flags: microcmdAuthUpdateLdapSimpleAuth.Flags, + Flags: newMicrocmdAuthUpdateLdapSimpleAuth().Flags, Action: service.updateLdapSimpleAuth, } // Run it From d29b7858d7b242a6a1707c7078ad5f535b558559 Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Mon, 19 May 2025 17:50:24 +0200 Subject: [PATCH 03/18] align tests --- cmd/admin_user.go | 2 +- cmd/admin_user_create.go | 147 ++++++++++++++++++---------------- cmd/admin_user_create_test.go | 6 +- cmd/docs.go | 3 +- cmd/main.go | 10 +-- cmd/main_test.go | 14 ++-- 6 files changed, 93 insertions(+), 89 deletions(-) diff --git a/cmd/admin_user.go b/cmd/admin_user.go index 386c611270c6d..64213d59ea388 100644 --- a/cmd/admin_user.go +++ b/cmd/admin_user.go @@ -11,7 +11,7 @@ var subcmdUser = &cli.Command{ Name: "user", Usage: "Modify users", Commands: []*cli.Command{ - microcmdUserCreate, + microcmdUserCreate(), microcmdUserList, microcmdUserChangePassword, microcmdUserDelete, diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 864b2a670dba9..b96ab2c198403 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -19,73 +19,84 @@ import ( "github.com/urfave/cli/v3" ) -var microcmdUserCreate = &cli.Command{ - Name: "create", - Usage: "Create a new user in database", - Action: runCreateUser, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "name", - Usage: "Username. DEPRECATED: use username instead", +func microcmdUserCreate() *cli.Command { + return &cli.Command{ + Name: "create", + Usage: "Create a new user in database", + Action: runCreateUser, + MutuallyExclusiveFlags: []cli.MutuallyExclusiveFlags{ + { + Flags: [][]cli.Flag{ + []cli.Flag{ + &cli.StringFlag{ + Name: "name", + Usage: "Username. DEPRECATED: use username instead", + }, + &cli.StringFlag{ + Name: "username", + Usage: "Username", + }, + }, + }, + Required: true, + }, }, - &cli.StringFlag{ - Name: "username", - Usage: "Username", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "user-type", + Usage: "Set user's type: individual or bot", + Value: "individual", + }, + &cli.StringFlag{ + Name: "password", + Usage: "User password", + }, + &cli.StringFlag{ + Name: "email", + Usage: "User email address", + }, + &cli.BoolFlag{ + Name: "admin", + Usage: "User is an admin", + }, + &cli.BoolFlag{ + Name: "random-password", + Usage: "Generate a random password for the user", + }, + &cli.BoolFlag{ + Name: "must-change-password", + Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)", + HideDefault: true, + }, + &cli.IntFlag{ + Name: "random-password-length", + Usage: "Length of the random password to be generated", + Value: 12, + }, + &cli.BoolFlag{ + Name: "access-token", + Usage: "Generate access token for the user", + }, + &cli.StringFlag{ + Name: "access-token-name", + Usage: `Name of the generated access token`, + Value: "gitea-admin", + }, + &cli.StringFlag{ + Name: "access-token-scopes", + Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`, + Value: "all", + }, + &cli.BoolFlag{ + Name: "restricted", + Usage: "Make a restricted user account", + }, + &cli.StringFlag{ + Name: "fullname", + Usage: `The full, human-readable name of the user`, + }, }, - &cli.StringFlag{ - Name: "user-type", - Usage: "Set user's type: individual or bot", - Value: "individual", - }, - &cli.StringFlag{ - Name: "password", - Usage: "User password", - }, - &cli.StringFlag{ - Name: "email", - Usage: "User email address", - }, - &cli.BoolFlag{ - Name: "admin", - Usage: "User is an admin", - }, - &cli.BoolFlag{ - Name: "random-password", - Usage: "Generate a random password for the user", - }, - &cli.BoolFlag{ - Name: "must-change-password", - Usage: "User must change password after initial login, defaults to true for all users except the first one (can be disabled by --must-change-password=false)", - DefaultText: "", - }, - &cli.IntFlag{ - Name: "random-password-length", - Usage: "Length of the random password to be generated", - Value: 12, - }, - &cli.BoolFlag{ - Name: "access-token", - Usage: "Generate access token for the user", - }, - &cli.StringFlag{ - Name: "access-token-name", - Usage: `Name of the generated access token`, - Value: "gitea-admin", - }, - &cli.StringFlag{ - Name: "access-token-scopes", - Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`, - Value: "all", - }, - &cli.BoolFlag{ - Name: "restricted", - Usage: "Make a restricted user account", - }, - &cli.StringFlag{ - Name: "fullname", - Usage: `The full, human-readable name of the user`, - }, - }, + } } func runCreateUser(ctx context.Context, c *cli.Command) error { @@ -113,12 +124,6 @@ func runCreateUser(ctx context.Context, c *cli.Command) error { return errors.New("password can only be set for individual users") } } - if c.IsSet("name") && c.IsSet("username") { - return errors.New("cannot set both --name and --username flags") - } - if !c.IsSet("name") && !c.IsSet("username") { - return errors.New("one of --name or --username flags must be set") - } if c.IsSet("password") && c.IsSet("random-password") { return errors.New("cannot set both -random-password and -password flags") diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go index 5b95b40196347..d4da38f1cc0c3 100644 --- a/cmd/admin_user_create_test.go +++ b/cmd/admin_user_create_test.go @@ -18,7 +18,6 @@ import ( ) func TestAdminUserCreate(t *testing.T) { - app := NewMainApp(AppVersion{}) reset := func() { require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) @@ -31,8 +30,9 @@ func TestAdminUserCreate(t *testing.T) { IsAdmin bool MustChangePassword bool } + createCheck := func(name, args string) check { - require.NoError(t, app.Run(t.Context(), strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args)))) + require.NoError(t, microcmdUserCreate().Run(t.Context(), strings.Fields(fmt.Sprintf("create --username %s --email %s@gitea.local %s --password foobar", name, name, args)))) u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name}) return check{IsAdmin: u.IsAdmin, MustChangePassword: u.MustChangePassword} } @@ -51,7 +51,7 @@ func TestAdminUserCreate(t *testing.T) { }) createUser := func(name string, args ...string) error { - return app.Run(t.Context(), append([]string{"./gitea", "admin", "user", "create", "--username", name, "--email", name + "@gitea.local"}, args...)) + return microcmdUserCreate().Run(t.Context(), append([]string{"create", "--username", name, "--email", name + "@gitea.local"}, args...)) } t.Run("UserType", func(t *testing.T) { diff --git a/cmd/docs.go b/cmd/docs.go index 380b063448b53..622ac318021e9 100644 --- a/cmd/docs.go +++ b/cmd/docs.go @@ -9,9 +9,8 @@ import ( "os" "strings" - "github.com/urfave/cli/v3" - cli_docs "github.com/urfave/cli-docs/v3" + "github.com/urfave/cli/v3" ) // CmdDocs represents the available docs sub-command. diff --git a/cmd/main.go b/cmd/main.go index d5bbd9cac85d3..31ff9c9a5b879 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -23,18 +23,18 @@ func cmdHelp() *cli.Command { Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", - Action: func(_ context.Context, c *cli.Command) (err error) { - lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea, {Command:nil} + Action: func(ctx context.Context, c *cli.Command) (err error) { + lineage := c.Lineage() // The order is from child to parent: help, doctor, Gitea targetCmdIdx := 0 if c.Name == "help" { targetCmdIdx = 1 } - if lineage[targetCmdIdx+1] != nil { - err = cli.ShowCommandHelp(context.Background(), lineage[targetCmdIdx+1], lineage[targetCmdIdx].Name) + if lineage[targetCmdIdx].Name != "Gitea" { + err = cli.ShowCommandHelp(ctx, lineage[targetCmdIdx], lineage[targetCmdIdx].Name) } else { err = cli.ShowAppHelp(c) } - _, _ = fmt.Fprintf(c.Writer, ` + _, _ = fmt.Fprintf(c.Root().Writer, ` DEFAULT CONFIGURATION: AppPath: %s WorkPath: %s diff --git a/cmd/main_test.go b/cmd/main_test.go index 2d27eba98947a..96b1830a31b6c 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -28,7 +28,7 @@ func makePathOutput(workPath, customPath, customConf string) string { return fmt.Sprintf("WorkPath=%s\nCustomPath=%s\nCustomConf=%s", workPath, customPath, customConf) } -func newTestApp(testCmdAction func(ctx context.Context, cmd *cli.Command) error) *cli.Command { +func newTestApp(testCmdAction cli.ActionFunc) *cli.Command { app := NewMainApp(AppVersion{}) testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction} prepareSubcommandWithConfig(testCmd, appGlobalFlags()) @@ -110,12 +110,12 @@ func TestCliCmd(t *testing.T) { }, } - app := newTestApp(func(ctx context.Context, cmd *cli.Command) error { - _, _ = fmt.Fprint(cmd.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) - return nil - }) for _, c := range cases { t.Run(c.cmd, func(t *testing.T) { + app := newTestApp(func(ctx context.Context, cmd *cli.Command) error { + _, _ = fmt.Fprint(cmd.Root().Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) + return nil + }) for k, v := range c.env { t.Setenv(k, v) } @@ -147,8 +147,8 @@ func TestCliCmdError(t *testing.T) { r, err = runTestApp(app, "./gitea", "test-cmd", "--no-such") assert.Error(t, err) assert.Equal(t, 1, r.ExitCode) - assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stdout) - assert.Empty(t, r.Stderr) // the cli package's strange behavior, the error message is not in stderr .... + assert.Empty(t, r.Stdout) + assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stderr) app = newTestApp(func(ctx context.Context, cmd *cli.Command) error { return nil }) r, err = runTestApp(app, "./gitea", "test-cmd") From ffd04d01d89bf6f8a39d0692e9328f537d33ca6f Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Tue, 20 May 2025 19:30:25 +0200 Subject: [PATCH 04/18] add tests for smtp command --- cmd/admin.go | 4 +- cmd/admin_auth_smtp_test.go | 286 ++++++++++++++++++++++++++++++++++++ cmd/admin_auth_stmp.go | 45 +++--- 3 files changed, 313 insertions(+), 22 deletions(-) create mode 100644 cmd/admin_auth_smtp_test.go diff --git a/cmd/admin.go b/cmd/admin.go index 241579618d45c..1cca0b8603c66 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -57,8 +57,8 @@ var ( newMicrocmdAuthUpdateLdapBindDn(), newMicrocmdAuthAddLdapSimpleAuth(), newMicrocmdAuthUpdateLdapSimpleAuth(), - microcmdAuthAddSMTP, - microcmdAuthUpdateSMTP, + microcmdAuthAddSMTP(), + microcmdAuthUpdateSMTP(), microcmdAuthList, microcmdAuthDelete, }, diff --git a/cmd/admin_auth_smtp_test.go b/cmd/admin_auth_smtp_test.go new file mode 100644 index 0000000000000..febff2bd92420 --- /dev/null +++ b/cmd/admin_auth_smtp_test.go @@ -0,0 +1,286 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "context" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/services/auth/source/smtp" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v3" +) + +func TestAddSMTP(t *testing.T) { + testCases := []struct { + name string + args []string + source *auth_model.Source + errMsg string + }{ + { + name: "missing name", + args: []string{ + "--host", "localhost", + "--port", "25", + }, + errMsg: "name must be set", + }, + { + name: "missing host", + args: []string{ + "--name", "test", + "--port", "25", + }, + errMsg: "host must be set", + }, + { + name: "missing port", + args: []string{ + "--name", "test", + "--host", "localhost", + }, + errMsg: "port must be set", + }, + { + name: "valid config", + args: []string{ + "--name", "test", + "--host", "localhost", + "--port", "25", + }, + source: &auth_model.Source{ + Type: auth_model.SMTP, + Name: "test", + IsActive: true, + Cfg: &smtp.Source{ + Auth: "PLAIN", + Host: "localhost", + Port: 25, + // ForceSMTPS: true, + // SkipVerify: true, + }, + TwoFactorPolicy: "skip", + }, + }, + { + name: "valid config with options", + args: []string{ + "--name", "test", + "--host", "localhost", + "--port", "25", + "--auth-type", "LOGIN", + "--force-smtps=false", + "--skip-verify=false", + "--helo-hostname", "example.com", + "--disable-helo=false", + "--allowed-domains", "example.com,example.org", + "--skip-local-2fa=false", + "--active=false", + }, + source: &auth_model.Source{ + Type: auth_model.SMTP, + Name: "test", + IsActive: false, + Cfg: &smtp.Source{ + Auth: "LOGIN", + Host: "localhost", + Port: 25, + ForceSMTPS: false, + SkipVerify: false, + HeloHostname: "example.com", + DisableHelo: false, + AllowedDomains: "example.com,example.org", + }, + TwoFactorPolicy: "", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + a := &authService{ + initDB: func(ctx context.Context) error { + return nil + }, + createAuthSource: func(ctx context.Context, source *auth_model.Source) error { + assert.Equal(t, tc.source, source) + return nil + }, + } + + cmd := &cli.Command{ + Flags: microcmdAuthAddSMTP().Flags, + Action: a.runAddSMTP, + } + + args := []string{"smtp-test"} + args = append(args, tc.args...) + + t.Log(args) + err := cmd.Run(t.Context(), args) + + if tc.errMsg != "" { + assert.EqualError(t, err, tc.errMsg) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestUpdateSMTP(t *testing.T) { + testCases := []struct { + name string + args []string + existingAuthSource *auth_model.Source + authSource *auth_model.Source + errMsg string + }{ + { + name: "missing id", + args: []string{ + "--name", "test", + "--host", "localhost", + "--port", "25", + }, + errMsg: "--id flag is missing", + }, + { + name: "valid config", + existingAuthSource: &auth_model.Source{ + ID: 1, + Type: auth_model.SMTP, + Name: "old name", + IsActive: true, + Cfg: &smtp.Source{ + Auth: "PLAIN", + Host: "old host", + Port: 26, + ForceSMTPS: true, + SkipVerify: true, + }, + TwoFactorPolicy: "", + }, + args: []string{ + "--id", "1", + "--name", "test", + "--host", "localhost", + "--port", "25", + }, + authSource: &auth_model.Source{ + ID: 1, + Type: auth_model.SMTP, + Name: "test", + IsActive: true, + Cfg: &smtp.Source{ + Auth: "PLAIN", + Host: "localhost", + Port: 25, + ForceSMTPS: true, + SkipVerify: true, + }, + TwoFactorPolicy: "skip", + }, + }, + { + name: "valid config with options", + existingAuthSource: &auth_model.Source{ + ID: 1, + Type: auth_model.SMTP, + Name: "old name", + IsActive: true, + Cfg: &smtp.Source{ + Auth: "PLAIN", + Host: "old host", + Port: 26, + ForceSMTPS: true, + SkipVerify: true, + HeloHostname: "old.example.com", + DisableHelo: false, + AllowedDomains: "old.example.com", + }, + TwoFactorPolicy: "", + }, + args: []string{ + "--id", "1", + "--name", "test", + "--host", "localhost", + "--port", "25", + "--auth-type", "LOGIN", + "--force-smtps=false", + "--skip-verify=false", + "--helo-hostname", "example.com", + "--disable-helo=true", + "--allowed-domains", "example.com,example.org", + "--skip-local-2fa=true", + "--active=false", + }, + authSource: &auth_model.Source{ + ID: 1, + Type: auth_model.SMTP, + Name: "test", + IsActive: false, + Cfg: &smtp.Source{ + Auth: "LOGIN", + Host: "localhost", + Port: 25, + ForceSMTPS: false, + SkipVerify: false, + HeloHostname: "example.com", + DisableHelo: true, + AllowedDomains: "example.com,example.org", + }, + TwoFactorPolicy: "skip", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + a := &authService{ + initDB: func(ctx context.Context) error { + return nil + }, + getAuthSourceByID: func(ctx context.Context, id int64) (*auth_model.Source, error) { + return &auth_model.Source{ + ID: 1, + Type: auth_model.SMTP, + Name: "test", + IsActive: true, + Cfg: &smtp.Source{ + Auth: "PLAIN", + SkipVerify: true, + ForceSMTPS: true, + }, + TwoFactorPolicy: "skip", + }, nil + + }, + + updateAuthSource: func(ctx context.Context, source *auth_model.Source) error { + assert.Equal(t, tc.authSource, source) + return nil + }, + } + + app := &cli.Command{ + Flags: microcmdAuthUpdateSMTP().Flags, + Action: a.runUpdateSMTP, + } + args := []string{"smtp-tests"} + args = append(args, tc.args...) + + err := app.Run(t.Context(), args) + + if tc.errMsg != "" { + assert.EqualError(t, err, tc.errMsg) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/cmd/admin_auth_stmp.go b/cmd/admin_auth_stmp.go index 016cb5639a51d..31d59fa7f13a3 100644 --- a/cmd/admin_auth_stmp.go +++ b/cmd/admin_auth_stmp.go @@ -6,6 +6,7 @@ package cmd import ( "context" "errors" + "fmt" "strings" auth_model "code.gitea.io/gitea/models/auth" @@ -15,8 +16,8 @@ import ( "github.com/urfave/cli/v3" ) -var ( - smtpCLIFlags = []cli.Flag{ +func smtpCLIFlags() []cli.Flag { + return []cli.Flag{ &cli.StringFlag{ Name: "name", Value: "", @@ -72,21 +73,24 @@ var ( Value: true, }, } +} - microcmdAuthAddSMTP = &cli.Command{ - Name: "add-smtp", - Usage: "Add new SMTP authentication source", - Action: runAddSMTP, - Flags: smtpCLIFlags, - } - - microcmdAuthUpdateSMTP = &cli.Command{ +func microcmdAuthUpdateSMTP() *cli.Command { + return &cli.Command{ Name: "update-smtp", Usage: "Update existing SMTP authentication source", - Action: runUpdateSMTP, - Flags: append(smtpCLIFlags[:1], append([]cli.Flag{idFlag}, smtpCLIFlags[1:]...)...), + Action: newAuthService().runUpdateSMTP, + Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{idFlag}, smtpCLIFlags()[1:]...)...), } -) +} +func microcmdAuthAddSMTP() *cli.Command { + return &cli.Command{ + Name: "add-smtp", + Usage: "Add new SMTP authentication source", + Action: newAuthService().runAddSMTP, + Flags: smtpCLIFlags(), + } +} func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error { if c.IsSet("auth-type") { @@ -121,11 +125,11 @@ func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error { return nil } -func runAddSMTP(_ context.Context, c *cli.Command) error { +func (a *authService) runAddSMTP(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() - if err := initDB(ctx); err != nil { + if err := a.initDB(ctx); err != nil { return err } @@ -140,6 +144,7 @@ func runAddSMTP(_ context.Context, c *cli.Command) error { } active := true if c.IsSet("active") { + fmt.Println("Active is set!", c.Bool("active")) active = c.Bool("active") } @@ -153,7 +158,7 @@ func runAddSMTP(_ context.Context, c *cli.Command) error { smtpConfig.Auth = "PLAIN" } - return auth_model.CreateSource(ctx, &auth_model.Source{ + return a.createAuthSource(ctx, &auth_model.Source{ Type: auth_model.SMTP, Name: c.String("name"), IsActive: active, @@ -162,7 +167,7 @@ func runAddSMTP(_ context.Context, c *cli.Command) error { }) } -func runUpdateSMTP(_ context.Context, c *cli.Command) error { +func (a *authService) runUpdateSMTP(_ context.Context, c *cli.Command) error { if !c.IsSet("id") { return errors.New("--id flag is missing") } @@ -170,11 +175,11 @@ func runUpdateSMTP(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() - if err := initDB(ctx); err != nil { + if err := a.initDB(ctx); err != nil { return err } - source, err := auth_model.GetSourceByID(ctx, c.Int64("id")) + source, err := a.getAuthSourceByID(ctx, c.Int64("id")) if err != nil { return err } @@ -195,5 +200,5 @@ func runUpdateSMTP(_ context.Context, c *cli.Command) error { source.Cfg = smtpConfig source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "") - return auth_model.UpdateSource(ctx, source) + return a.updateAuthSource(ctx, source) } From 553b43ac8da9aee52e4859c8f6093e092ae2eafb Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Wed, 21 May 2025 00:16:33 +0200 Subject: [PATCH 05/18] add oauth coverage patch smtp idflag persisting --- cmd/admin.go | 4 +- cmd/admin_auth_oauth.go | 39 +- cmd/admin_auth_oauth_test.go | 333 ++++++++++++++++++ ...{admin_auth_stmp.go => admin_auth_smtp.go} | 5 +- 4 files changed, 362 insertions(+), 19 deletions(-) create mode 100644 cmd/admin_auth_oauth_test.go rename cmd/{admin_auth_stmp.go => admin_auth_smtp.go} (96%) diff --git a/cmd/admin.go b/cmd/admin.go index 1cca0b8603c66..cdd7a4c2e1ca0 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -51,8 +51,8 @@ var ( Name: "auth", Usage: "Modify external auth providers", Commands: []*cli.Command{ - microcmdAuthAddOauth, - microcmdAuthUpdateOauth, + microcmdAuthAddOauth(), + microcmdAuthUpdateOauth(), newMicrocmdAuthAddLdapBindDn(), newMicrocmdAuthUpdateLdapBindDn(), newMicrocmdAuthAddLdapSimpleAuth(), diff --git a/cmd/admin_auth_oauth.go b/cmd/admin_auth_oauth.go index 091531686620a..02152a084b75a 100644 --- a/cmd/admin_auth_oauth.go +++ b/cmd/admin_auth_oauth.go @@ -16,8 +16,8 @@ import ( "github.com/urfave/cli/v3" ) -var ( - oauthCLIFlags = []cli.Flag{ +func oauthCLIFlags() []cli.Flag { + return []cli.Flag{ &cli.StringFlag{ Name: "name", Value: "", @@ -122,21 +122,28 @@ var ( Usage: "Activate automatic team membership removal depending on groups", }, } +} - microcmdAuthAddOauth = &cli.Command{ +func microcmdAuthAddOauth() *cli.Command { + return &cli.Command{ Name: "add-oauth", Usage: "Add new Oauth authentication source", - Action: runAddOauth, - Flags: oauthCLIFlags, + Action: newAuthService().runAddOauth, + Flags: oauthCLIFlags(), } +} - microcmdAuthUpdateOauth = &cli.Command{ +func microcmdAuthUpdateOauth() *cli.Command { + return &cli.Command{ Name: "update-oauth", Usage: "Update existing Oauth authentication source", - Action: runUpdateOauth, - Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...), + Action: newAuthService().runUpdateOauth, + Flags: append(oauthCLIFlags()[:1], append([]cli.Flag{&cli.Int64Flag{ + Name: "id", + Usage: "ID of authentication source", + }}, oauthCLIFlags()[1:]...)...), } -) +} func parseOAuth2Config(c *cli.Command) *oauth2.Source { var customURLMapping *oauth2.CustomURLMapping @@ -169,11 +176,11 @@ func parseOAuth2Config(c *cli.Command) *oauth2.Source { } } -func runAddOauth(_ context.Context, c *cli.Command) error { +func (a *authService) runAddOauth(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() - if err := initDB(ctx); err != nil { + if err := a.initDB(ctx); err != nil { return err } @@ -185,7 +192,7 @@ func runAddOauth(_ context.Context, c *cli.Command) error { } } - return auth_model.CreateSource(ctx, &auth_model.Source{ + return a.createAuthSource(ctx, &auth_model.Source{ Type: auth_model.OAuth2, Name: c.String("name"), IsActive: true, @@ -194,7 +201,7 @@ func runAddOauth(_ context.Context, c *cli.Command) error { }) } -func runUpdateOauth(_ context.Context, c *cli.Command) error { +func (a *authService) runUpdateOauth(_ context.Context, c *cli.Command) error { if !c.IsSet("id") { return errors.New("--id flag is missing") } @@ -202,11 +209,11 @@ func runUpdateOauth(_ context.Context, c *cli.Command) error { ctx, cancel := installSignals() defer cancel() - if err := initDB(ctx); err != nil { + if err := a.initDB(ctx); err != nil { return err } - source, err := auth_model.GetSourceByID(ctx, c.Int64("id")) + source, err := a.getAuthSourceByID(ctx, c.Int64("id")) if err != nil { return err } @@ -297,5 +304,5 @@ func runUpdateOauth(_ context.Context, c *cli.Command) error { oAuth2Config.CustomURLMapping = customURLMapping source.Cfg = oAuth2Config source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "") - return auth_model.UpdateSource(ctx, source) + return a.updateAuthSource(ctx, source) } diff --git a/cmd/admin_auth_oauth_test.go b/cmd/admin_auth_oauth_test.go new file mode 100644 index 0000000000000..df1bd9c1a6d01 --- /dev/null +++ b/cmd/admin_auth_oauth_test.go @@ -0,0 +1,333 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "context" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/services/auth/source/oauth2" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v3" +) + +func TestAddOauth(t *testing.T) { + testCases := []struct { + name string + args []string + source *auth_model.Source + errMsg string + }{ + { + name: "valid config", + args: []string{ + "--name", "test", + "--provider", "github", + "--key", "some_key", + "--secret", "some_secret", + }, + source: &auth_model.Source{ + Type: auth_model.OAuth2, + Name: "test", + IsActive: true, + Cfg: &oauth2.Source{ + Scopes: []string{}, + Provider: "github", + ClientID: "some_key", + ClientSecret: "some_secret", + }, + TwoFactorPolicy: "", + }, + }, + { + name: "valid config with openid connect", + args: []string{ + "--name", "test", + "--provider", "openidConnect", + "--key", "some_key", + "--secret", "some_secret", + "--auto-discover-url", "https://example.com", + }, + source: &auth_model.Source{ + Type: auth_model.OAuth2, + Name: "test", + IsActive: true, + Cfg: &oauth2.Source{ + Scopes: []string{}, + Provider: "openidConnect", + ClientID: "some_key", + ClientSecret: "some_secret", + OpenIDConnectAutoDiscoveryURL: "https://example.com", + }, + TwoFactorPolicy: "", + }, + }, + { + name: "valid config with options", + args: []string{ + "--name", "test", + "--provider", "gitlab", + "--key", "some_key", + "--secret", "some_secret", + "--use-custom-urls", "true", + "--custom-token-url", "https://example.com/token", + "--custom-auth-url", "https://example.com/auth", + "--custom-profile-url", "https://example.com/profile", + "--custom-email-url", "https://example.com/email", + "--custom-tenant-id", "some_tenant", + "--icon-url", "https://example.com/icon", + "--scopes", "scope1,scope2", + "--skip-local-2fa", "true", + "--required-claim-name", "claim_name", + "--required-claim-value", "claim_value", + "--group-claim-name", "group_name", + "--admin-group", "admin", + "--restricted-group", "restricted", + "--group-team-map", `{"group1": [1,2]}`, + "--group-team-map-removal=true", + }, + source: &auth_model.Source{ + Type: auth_model.OAuth2, + Name: "test", + IsActive: true, + Cfg: &oauth2.Source{ + Provider: "gitlab", + ClientID: "some_key", + ClientSecret: "some_secret", + CustomURLMapping: &oauth2.CustomURLMapping{ + TokenURL: "https://example.com/token", + AuthURL: "https://example.com/auth", + ProfileURL: "https://example.com/profile", + EmailURL: "https://example.com/email", + Tenant: "some_tenant", + }, + IconURL: "https://example.com/icon", + Scopes: []string{"scope1", "scope2"}, + RequiredClaimName: "claim_name", + RequiredClaimValue: "claim_value", + GroupClaimName: "group_name", + AdminGroup: "admin", + RestrictedGroup: "restricted", + GroupTeamMap: `{"group1": [1,2]}`, + GroupTeamMapRemoval: true, + }, + TwoFactorPolicy: "skip", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var createdSource *auth_model.Source + a := &authService{ + initDB: func(ctx context.Context) error { + return nil + }, + createAuthSource: func(ctx context.Context, source *auth_model.Source) error { + createdSource = source + return nil + }, + } + + app := &cli.Command{ + Flags: microcmdAuthAddOauth().Flags, + Action: a.runAddOauth, + } + + args := []string{"oauth-test"} + args = append(args, tc.args...) + + err := app.Run(t.Context(), args) + + if tc.errMsg != "" { + assert.EqualError(t, err, tc.errMsg) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.source, createdSource) + } + }) + } +} + +func TestUpdateOauth(t *testing.T) { + testCases := []struct { + name string + args []string + id int64 + existingAuthSource *auth_model.Source + authSource *auth_model.Source + errMsg string + }{ + { + name: "missing id", + args: []string{ + "--name", "test", + }, + errMsg: "--id flag is missing", + }, + { + name: "valid config", + id: 1, + existingAuthSource: &auth_model.Source{ + ID: 1, + Type: auth_model.OAuth2, + Name: "old name", + IsActive: true, + Cfg: &oauth2.Source{ + Provider: "github", + ClientID: "old_key", + ClientSecret: "old_secret", + }, + TwoFactorPolicy: "", + }, + args: []string{ + "--id", "1", + "--name", "test", + "--provider", "gitlab", + "--key", "new_key", + "--secret", "new_secret", + }, + authSource: &auth_model.Source{ + ID: 1, + Type: auth_model.OAuth2, + Name: "test", + IsActive: true, + Cfg: &oauth2.Source{ + Provider: "gitlab", + ClientID: "new_key", + ClientSecret: "new_secret", + CustomURLMapping: &oauth2.CustomURLMapping{}, + }, + TwoFactorPolicy: "", + }, + }, + { + name: "valid config with options", + id: 1, + existingAuthSource: &auth_model.Source{ + ID: 1, + Type: auth_model.OAuth2, + Name: "old name", + IsActive: true, + Cfg: &oauth2.Source{ + Provider: "gitlab", + ClientID: "old_key", + ClientSecret: "old_secret", + CustomURLMapping: &oauth2.CustomURLMapping{ + TokenURL: "https://old.example.com/token", + AuthURL: "https://old.example.com/auth", + ProfileURL: "https://old.example.com/profile", + EmailURL: "https://old.example.com/email", + Tenant: "old_tenant", + }, + IconURL: "https://old.example.com/icon", + Scopes: []string{"old_scope1", "old_scope2"}, + RequiredClaimName: "old_claim_name", + RequiredClaimValue: "old_claim_value", + GroupClaimName: "old_group_name", + AdminGroup: "old_admin", + RestrictedGroup: "old_restricted", + GroupTeamMap: `{"old_group1": [1,2]}`, + GroupTeamMapRemoval: true, + }, + TwoFactorPolicy: "", + }, + args: []string{ + "--id", "1", + "--name", "test", + "--provider", "github", + "--key", "new_key", + "--secret", "new_secret", + "--use-custom-urls", "true", + "--custom-token-url", "https://example.com/token", + "--custom-auth-url", "https://example.com/auth", + "--custom-profile-url", "https://example.com/profile", + "--custom-email-url", "https://example.com/email", + "--custom-tenant-id", "new_tenant", + "--icon-url", "https://example.com/icon", + "--scopes", "scope1,scope2", + "--skip-local-2fa=true", + "--required-claim-name", "claim_name", + "--required-claim-value", "claim_value", + "--group-claim-name", "group_name", + "--admin-group", "admin", + "--restricted-group", "restricted", + "--group-team-map", `{"group1": [1,2]}`, + "--group-team-map-removal=false", + }, + authSource: &auth_model.Source{ + ID: 1, + Type: auth_model.OAuth2, + Name: "test", + IsActive: true, + Cfg: &oauth2.Source{ + Provider: "github", + ClientID: "new_key", + ClientSecret: "new_secret", + CustomURLMapping: &oauth2.CustomURLMapping{ + TokenURL: "https://example.com/token", + AuthURL: "https://example.com/auth", + ProfileURL: "https://example.com/profile", + EmailURL: "https://example.com/email", + Tenant: "new_tenant", + }, + IconURL: "https://example.com/icon", + Scopes: []string{"scope1", "scope2"}, + RequiredClaimName: "claim_name", + RequiredClaimValue: "claim_value", + GroupClaimName: "group_name", + AdminGroup: "admin", + RestrictedGroup: "restricted", + GroupTeamMap: `{"group1": [1,2]}`, + GroupTeamMapRemoval: false, + }, + TwoFactorPolicy: "skip", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + a := &authService{ + initDB: func(ctx context.Context) error { + return nil + }, + getAuthSourceByID: func(ctx context.Context, id int64) (*auth_model.Source, error) { + return &auth_model.Source{ + ID: 1, + Type: auth_model.OAuth2, + Name: "test", + IsActive: true, + Cfg: &oauth2.Source{ + CustomURLMapping: &oauth2.CustomURLMapping{}, + }, + TwoFactorPolicy: "skip", + }, nil + }, + updateAuthSource: func(ctx context.Context, source *auth_model.Source) error { + assert.Equal(t, tc.authSource, source) + return nil + }, + } + + app := &cli.Command{ + Flags: microcmdAuthUpdateOauth().Flags, + Action: a.runUpdateOauth, + } + + args := []string{"oauth-test"} + args = append(args, tc.args...) + + err := app.Run(t.Context(), args) + + if tc.errMsg != "" { + assert.EqualError(t, err, tc.errMsg) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/cmd/admin_auth_stmp.go b/cmd/admin_auth_smtp.go similarity index 96% rename from cmd/admin_auth_stmp.go rename to cmd/admin_auth_smtp.go index 31d59fa7f13a3..cf14707ae1487 100644 --- a/cmd/admin_auth_stmp.go +++ b/cmd/admin_auth_smtp.go @@ -80,7 +80,10 @@ func microcmdAuthUpdateSMTP() *cli.Command { Name: "update-smtp", Usage: "Update existing SMTP authentication source", Action: newAuthService().runUpdateSMTP, - Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{idFlag}, smtpCLIFlags()[1:]...)...), + Flags: append(smtpCLIFlags()[:1], append([]cli.Flag{&cli.Int64Flag{ + Name: "id", + Usage: "ID of authentication source", + }}, smtpCLIFlags()[1:]...)...), } } func microcmdAuthAddSMTP() *cli.Command { From 69cde4d79a45e2f22ed96c4741b327f0bae51fcf Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Wed, 21 May 2025 00:30:52 +0200 Subject: [PATCH 06/18] fix formatting --- cmd/admin_auth_ldap.go | 6 ++++-- cmd/admin_auth_smtp.go | 1 + cmd/admin_auth_smtp_test.go | 1 - cmd/admin_user_create.go | 2 +- cmd/admin_user_create_test.go | 1 - 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index e36c64ba87392..57f06175da99e 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -140,7 +140,8 @@ func newMicrocmdAuthAddLdapSimpleAuth() *cli.Command { &cli.StringFlag{Name: "public-ssh-key-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key."}, &cli.BoolFlag{Name: "skip-local-2fa", Usage: "Set to true to skip local 2fa for users authenticated by this source"}, &cli.StringFlag{Name: "avatar-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s avatar."}, - &cli.StringFlag{Name: "user-dn", Usage: "The user's DN.", Required: true}}, + &cli.StringFlag{Name: "user-dn", Usage: "The user's DN.", Required: true}, + }, } } @@ -173,7 +174,8 @@ func newMicrocmdAuthUpdateLdapSimpleAuth() *cli.Command { &cli.BoolFlag{Name: "skip-local-2fa", Usage: "Set to true to skip local 2fa for users authenticated by this source"}, &cli.StringFlag{Name: "avatar-attribute", Usage: "The attribute of the user’s LDAP record containing the user’s avatar."}, &cli.StringFlag{Name: "user-dn", Usage: "The user's DN."}, - }} + }, + } } // newAuthService creates a service with default functions. diff --git a/cmd/admin_auth_smtp.go b/cmd/admin_auth_smtp.go index cf14707ae1487..ad6b98715482a 100644 --- a/cmd/admin_auth_smtp.go +++ b/cmd/admin_auth_smtp.go @@ -86,6 +86,7 @@ func microcmdAuthUpdateSMTP() *cli.Command { }}, smtpCLIFlags()[1:]...)...), } } + func microcmdAuthAddSMTP() *cli.Command { return &cli.Command{ Name: "add-smtp", diff --git a/cmd/admin_auth_smtp_test.go b/cmd/admin_auth_smtp_test.go index febff2bd92420..9778ff87d2bbf 100644 --- a/cmd/admin_auth_smtp_test.go +++ b/cmd/admin_auth_smtp_test.go @@ -258,7 +258,6 @@ func TestUpdateSMTP(t *testing.T) { }, TwoFactorPolicy: "skip", }, nil - }, updateAuthSource: func(ctx context.Context, source *auth_model.Source) error { diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index b96ab2c198403..28103c3d42fd8 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -27,7 +27,7 @@ func microcmdUserCreate() *cli.Command { MutuallyExclusiveFlags: []cli.MutuallyExclusiveFlags{ { Flags: [][]cli.Flag{ - []cli.Flag{ + { &cli.StringFlag{ Name: "name", Usage: "Username. DEPRECATED: use username instead", diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go index d4da38f1cc0c3..437e07d9a28ec 100644 --- a/cmd/admin_user_create_test.go +++ b/cmd/admin_user_create_test.go @@ -18,7 +18,6 @@ import ( ) func TestAdminUserCreate(t *testing.T) { - reset := func() { require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{})) From 4ae57e83b3c49e83de8e24ceee3d0476201ff066 Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Fri, 23 May 2025 22:28:00 +0200 Subject: [PATCH 07/18] install signals in main --- cmd/actions.go | 5 +-- cmd/admin.go | 5 +-- cmd/admin_auth.go | 10 ++---- cmd/admin_auth_ldap.go | 20 +++--------- cmd/admin_auth_oauth.go | 10 ++---- cmd/admin_auth_smtp.go | 10 ++---- cmd/admin_regenerate.go | 10 ++---- cmd/admin_user_change_password.go | 5 +-- cmd/admin_user_create.go | 3 -- cmd/admin_user_delete.go | 5 +-- cmd/admin_user_generate_access_token.go | 5 +-- cmd/admin_user_list.go | 5 +-- cmd/admin_user_must_change_password.go | 5 +-- cmd/doctor.go | 16 +++------- cmd/doctor_convert.go | 7 ++--- cmd/dump.go | 7 ++--- cmd/dump_repo.go | 7 ++--- cmd/hook.go | 14 ++------- cmd/keys.go | 5 +-- cmd/mailer.go | 5 +-- cmd/main.go | 4 ++- cmd/manager.go | 25 +++------------ cmd/manager_logging.go | 42 ++++++------------------- cmd/migrate.go | 7 ++--- cmd/migrate_storage.go | 15 ++++----- cmd/restore_repo.go | 5 +-- cmd/serv.go | 5 +-- contrib/backport/backport.go | 10 +++--- 28 files changed, 67 insertions(+), 205 deletions(-) diff --git a/cmd/actions.go b/cmd/actions.go index 240e17f35ecdd..2c51c6a1bcce0 100644 --- a/cmd/actions.go +++ b/cmd/actions.go @@ -39,10 +39,7 @@ var ( } ) -func runGenerateActionsRunnerToken(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runGenerateActionsRunnerToken(ctx context.Context, c *cli.Command) error { setting.MustInstalled() scope := c.String("scope") diff --git a/cmd/admin.go b/cmd/admin.go index cdd7a4c2e1ca0..c38357625e9ef 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -93,10 +93,7 @@ var ( } ) -func runRepoSyncReleases(_ context.Context, _ *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error { if err := initDB(ctx); err != nil { return err } diff --git a/cmd/admin_auth.go b/cmd/admin_auth.go index eff1532b2f694..eefa981428127 100644 --- a/cmd/admin_auth.go +++ b/cmd/admin_auth.go @@ -57,10 +57,7 @@ var ( } ) -func runListAuth(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runListAuth(ctx context.Context, c *cli.Command) error { if err := initDB(ctx); err != nil { return err } @@ -91,14 +88,11 @@ func runListAuth(_ context.Context, c *cli.Command) error { return nil } -func runDeleteAuth(_ context.Context, c *cli.Command) error { +func runDeleteAuth(ctx context.Context, c *cli.Command) error { if !c.IsSet("id") { return errors.New("--id flag is missing") } - ctx, cancel := installSignals() - defer cancel() - if err := initDB(ctx); err != nil { return err } diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index 57f06175da99e..80786282e8df1 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -328,10 +328,7 @@ func (a *authService) getAuthSource(ctx context.Context, c *cli.Command, authTyp } // addLdapBindDn adds a new LDAP via Bind DN authentication source. -func (a *authService) addLdapBindDn(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func (a *authService) addLdapBindDn(ctx context.Context, c *cli.Command) error { if err := a.initDB(ctx); err != nil { return err } @@ -353,10 +350,7 @@ func (a *authService) addLdapBindDn(_ context.Context, c *cli.Command) error { } // updateLdapBindDn updates a new LDAP via Bind DN authentication source. -func (a *authService) updateLdapBindDn(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) error { if err := a.initDB(ctx); err != nil { return err } @@ -375,10 +369,7 @@ func (a *authService) updateLdapBindDn(_ context.Context, c *cli.Command) error } // addLdapSimpleAuth adds a new LDAP (simple auth) authentication source. -func (a *authService) addLdapSimpleAuth(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) error { if err := a.initDB(ctx); err != nil { return err } @@ -400,10 +391,7 @@ func (a *authService) addLdapSimpleAuth(_ context.Context, c *cli.Command) error } // updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source. -func (a *authService) updateLdapSimpleAuth(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func (a *authService) updateLdapSimpleAuth(ctx context.Context, c *cli.Command) error { if err := a.initDB(ctx); err != nil { return err } diff --git a/cmd/admin_auth_oauth.go b/cmd/admin_auth_oauth.go index 02152a084b75a..05af1c3a20753 100644 --- a/cmd/admin_auth_oauth.go +++ b/cmd/admin_auth_oauth.go @@ -176,10 +176,7 @@ func parseOAuth2Config(c *cli.Command) *oauth2.Source { } } -func (a *authService) runAddOauth(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func (a *authService) runAddOauth(ctx context.Context, c *cli.Command) error { if err := a.initDB(ctx); err != nil { return err } @@ -201,14 +198,11 @@ func (a *authService) runAddOauth(_ context.Context, c *cli.Command) error { }) } -func (a *authService) runUpdateOauth(_ context.Context, c *cli.Command) error { +func (a *authService) runUpdateOauth(ctx context.Context, c *cli.Command) error { if !c.IsSet("id") { return errors.New("--id flag is missing") } - ctx, cancel := installSignals() - defer cancel() - if err := a.initDB(ctx); err != nil { return err } diff --git a/cmd/admin_auth_smtp.go b/cmd/admin_auth_smtp.go index ad6b98715482a..457aa2f5c0405 100644 --- a/cmd/admin_auth_smtp.go +++ b/cmd/admin_auth_smtp.go @@ -129,10 +129,7 @@ func parseSMTPConfig(c *cli.Command, conf *smtp.Source) error { return nil } -func (a *authService) runAddSMTP(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func (a *authService) runAddSMTP(ctx context.Context, c *cli.Command) error { if err := a.initDB(ctx); err != nil { return err } @@ -171,14 +168,11 @@ func (a *authService) runAddSMTP(_ context.Context, c *cli.Command) error { }) } -func (a *authService) runUpdateSMTP(_ context.Context, c *cli.Command) error { +func (a *authService) runUpdateSMTP(ctx context.Context, c *cli.Command) error { if !c.IsSet("id") { return errors.New("--id flag is missing") } - ctx, cancel := installSignals() - defer cancel() - if err := a.initDB(ctx); err != nil { return err } diff --git a/cmd/admin_regenerate.go b/cmd/admin_regenerate.go index a5c93364f8087..a5f1bd5105986 100644 --- a/cmd/admin_regenerate.go +++ b/cmd/admin_regenerate.go @@ -27,20 +27,14 @@ var ( } ) -func runRegenerateHooks(_ context.Context, _ *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runRegenerateHooks(ctx context.Context, _ *cli.Command) error { if err := initDB(ctx); err != nil { return err } return repo_service.SyncRepositoryHooks(graceful.GetManager().ShutdownContext()) } -func runRegenerateKeys(_ context.Context, _ *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runRegenerateKeys(ctx context.Context, _ *cli.Command) error { if err := initDB(ctx); err != nil { return err } diff --git a/cmd/admin_user_change_password.go b/cmd/admin_user_change_password.go index f7c05ad647679..3c4357ec0a2d8 100644 --- a/cmd/admin_user_change_password.go +++ b/cmd/admin_user_change_password.go @@ -42,14 +42,11 @@ var microcmdUserChangePassword = &cli.Command{ }, } -func runChangePassword(_ context.Context, c *cli.Command) error { +func runChangePassword(ctx context.Context, c *cli.Command) error { if err := argsSet(c, "username", "password"); err != nil { return err } - ctx, cancel := installSignals() - defer cancel() - if err := initDB(ctx); err != nil { return err } diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 28103c3d42fd8..4f68c932e0946 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -140,9 +140,6 @@ func runCreateUser(ctx context.Context, c *cli.Command) error { if !setting.IsInTesting { // FIXME: need to refactor the "installSignals/initDB" related code later // it doesn't make sense to call it in (almost) every command action function - var cancel context.CancelFunc - ctx, cancel = installSignals() - defer cancel() if err := initDB(ctx); err != nil { return err } diff --git a/cmd/admin_user_delete.go b/cmd/admin_user_delete.go index 18317622cf330..6fe1213d41b61 100644 --- a/cmd/admin_user_delete.go +++ b/cmd/admin_user_delete.go @@ -42,14 +42,11 @@ var microcmdUserDelete = &cli.Command{ Action: runDeleteUser, } -func runDeleteUser(_ context.Context, c *cli.Command) error { +func runDeleteUser(ctx context.Context, c *cli.Command) error { if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") { return errors.New("You must provide the id, username or email of a user to delete") } - ctx, cancel := installSignals() - defer cancel() - if err := initDB(ctx); err != nil { return err } diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go index 901ac5e1cbc26..61064fdef4b6a 100644 --- a/cmd/admin_user_generate_access_token.go +++ b/cmd/admin_user_generate_access_token.go @@ -42,14 +42,11 @@ var microcmdUserGenerateAccessToken = &cli.Command{ Action: runGenerateAccessToken, } -func runGenerateAccessToken(_ context.Context, c *cli.Command) error { +func runGenerateAccessToken(ctx context.Context, c *cli.Command) error { if !c.IsSet("username") { return errors.New("you must provide a username to generate a token for") } - ctx, cancel := installSignals() - defer cancel() - if err := initDB(ctx); err != nil { return err } diff --git a/cmd/admin_user_list.go b/cmd/admin_user_list.go index ec214345fc3dd..e3d345e2f248c 100644 --- a/cmd/admin_user_list.go +++ b/cmd/admin_user_list.go @@ -26,10 +26,7 @@ var microcmdUserList = &cli.Command{ }, } -func runListUsers(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runListUsers(ctx context.Context, c *cli.Command) error { if err := initDB(ctx); err != nil { return err } diff --git a/cmd/admin_user_must_change_password.go b/cmd/admin_user_must_change_password.go index ddc82af4161aa..677477ca92d75 100644 --- a/cmd/admin_user_must_change_password.go +++ b/cmd/admin_user_must_change_password.go @@ -35,10 +35,7 @@ var microcmdUserMustChangePassword = &cli.Command{ }, } -func runMustChangePassword(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runMustChangePassword(ctx context.Context, c *cli.Command) error { if c.NArg() == 0 && !c.IsSet("all") { return errors.New("either usernames or --all must be provided") } diff --git a/cmd/doctor.go b/cmd/doctor.go index 688793bcfa9a7..9e0fcbf8779f0 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -93,10 +93,7 @@ You should back-up your database before doing this and ensure that your database Action: runRecreateTable, } -func runRecreateTable(_ context.Context, cmd *cli.Command) error { - stdCtx, cancel := installSignals() - defer cancel() - +func runRecreateTable(ctx context.Context, cmd *cli.Command) error { // Redirect the default golog to here golog.SetFlags(0) golog.SetPrefix("") @@ -113,7 +110,7 @@ func runRecreateTable(_ context.Context, cmd *cli.Command) error { } setting.Database.LogSQL = debug - if err := db.InitEngine(stdCtx); err != nil { + if err := db.InitEngine(ctx); err != nil { fmt.Println(err) fmt.Println("Check if you are using the right config file. You can use a --config directive to specify one.") return nil @@ -131,7 +128,7 @@ func runRecreateTable(_ context.Context, cmd *cli.Command) error { } recreateTables := migrate_base.RecreateTables(beans...) - return db.InitEngineWithMigration(stdCtx, func(ctx context.Context, x *xorm.Engine) error { + return db.InitEngineWithMigration(ctx, func(ctx context.Context, x *xorm.Engine) error { if err := migrations.EnsureUpToDate(ctx, x); err != nil { return err } @@ -161,10 +158,7 @@ func setupDoctorDefaultLogger(cmd *cli.Command, colorize bool) { } } -func runDoctorCheck(_ context.Context, cmd *cli.Command) error { - stdCtx, cancel := installSignals() - defer cancel() - +func runDoctorCheck(ctx context.Context, cmd *cli.Command) error { colorize := log.CanColorStdout if cmd.IsSet("color") { colorize = cmd.Bool("color") @@ -217,5 +211,5 @@ func runDoctorCheck(_ context.Context, cmd *cli.Command) error { } } } - return doctor.RunChecks(stdCtx, colorize, cmd.Bool("fix"), checks) + return doctor.RunChecks(ctx, colorize, cmd.Bool("fix"), checks) } diff --git a/cmd/doctor_convert.go b/cmd/doctor_convert.go index deb989128e613..8cb718d383953 100644 --- a/cmd/doctor_convert.go +++ b/cmd/doctor_convert.go @@ -22,11 +22,8 @@ var cmdDoctorConvert = &cli.Command{ Action: runDoctorConvert, } -func runDoctorConvert(_ context.Context, cmd *cli.Command) error { - stdCtx, cancel := installSignals() - defer cancel() - - if err := initDB(stdCtx); err != nil { +func runDoctorConvert(ctx context.Context, cmd *cli.Command) error { + if err := initDB(ctx); err != nil { return err } diff --git a/cmd/dump.go b/cmd/dump.go index 8ae278bd7040e..ed19e3d4bf00f 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -102,7 +102,7 @@ func fatal(format string, args ...any) { log.Fatal(format, args...) } -func runDump(_ context.Context, cmd *cli.Command) error { +func runDump(ctx context.Context, cmd *cli.Command) error { setting.MustInstalled() quite := cmd.Bool("quiet") @@ -137,10 +137,7 @@ func runDump(_ context.Context, cmd *cli.Command) error { setting.DisableLoggerInit() setting.LoadSettings() // cannot access session settings otherwise - stdCtx, cancel := installSignals() - defer cancel() - - err := db.InitEngine(stdCtx) + err := db.InitEngine(ctx) if err != nil { return err } diff --git a/cmd/dump_repo.go b/cmd/dump_repo.go index 4b125e5f695d5..ae25ad2d794b4 100644 --- a/cmd/dump_repo.go +++ b/cmd/dump_repo.go @@ -79,11 +79,8 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme }, } -func runDumpRepository(_ context.Context, cmd *cli.Command) error { - stdCtx, cancel := installSignals() - defer cancel() - - if err := initDB(stdCtx); err != nil { +func runDumpRepository(ctx context.Context, cmd *cli.Command) error { + if err := initDB(ctx); err != nil { return err } diff --git a/cmd/hook.go b/cmd/hook.go index 7a9f8c84a45b4..b9e9f8781588c 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -161,12 +161,10 @@ func (n *nilWriter) WriteString(s string) (int, error) { return len(s), nil } -func runHookPreReceive(_ context.Context, c *cli.Command) error { +func runHookPreReceive(ctx context.Context, c *cli.Command) error { if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { return nil } - ctx, cancel := installSignals() - defer cancel() setup(ctx, c.Bool("debug")) @@ -309,10 +307,7 @@ func runHookUpdate(_ context.Context, c *cli.Command) error { return nil } -func runHookPostReceive(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runHookPostReceive(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) // First of all run update-server-info no matter what @@ -496,10 +491,7 @@ func pushOptions() map[string]string { return opts } -func runHookProcReceive(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runHookProcReceive(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { diff --git a/cmd/keys.go b/cmd/keys.go index a63025be6b482..759932f1e9ee8 100644 --- a/cmd/keys.go +++ b/cmd/keys.go @@ -50,7 +50,7 @@ var CmdKeys = &cli.Command{ }, } -func runKeys(_ context.Context, c *cli.Command) error { +func runKeys(ctx context.Context, c *cli.Command) error { if !c.IsSet("username") { return errors.New("No username provided") } @@ -69,9 +69,6 @@ func runKeys(_ context.Context, c *cli.Command) error { return errors.New("No key type and content provided") } - ctx, cancel := installSignals() - defer cancel() - setup(ctx, c.Bool("debug")) authorizedString, extra := private.AuthorizedPublicKeyByContent(ctx, content) diff --git a/cmd/mailer.go b/cmd/mailer.go index 7721c8db914f6..e0bbd91ad665d 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -13,10 +13,7 @@ import ( "github.com/urfave/cli/v3" ) -func runSendMail(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runSendMail(ctx context.Context, c *cli.Command) error { setting.MustInstalled() if err := argsSet(c, "title"); err != nil { diff --git a/cmd/main.go b/cmd/main.go index 31ff9c9a5b879..d086fda275fe8 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -173,7 +173,9 @@ func NewMainApp(appVer AppVersion) *cli.Command { } func RunMainApp(app *cli.Command, args ...string) error { - err := app.Run(context.Background(), args) + ctx, cancel := installSignals() + defer cancel() + err := app.Run(ctx, args) if err == nil { return nil } diff --git a/cmd/manager.go b/cmd/manager.go index c4b07e4f1cc41..f0935ea06570f 100644 --- a/cmd/manager.go +++ b/cmd/manager.go @@ -109,46 +109,31 @@ var ( } ) -func runShutdown(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runShutdown(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) extra := private.Shutdown(ctx) return handleCliResponseExtra(extra) } -func runRestart(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runRestart(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) extra := private.Restart(ctx) return handleCliResponseExtra(extra) } -func runReloadTemplates(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runReloadTemplates(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) extra := private.ReloadTemplates(ctx) return handleCliResponseExtra(extra) } -func runFlushQueues(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runFlushQueues(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) extra := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking")) return handleCliResponseExtra(extra) } -func runProcesses(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runProcesses(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) extra := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel")) return handleCliResponseExtra(extra) diff --git a/cmd/manager_logging.go b/cmd/manager_logging.go index cb54ebb5c4922..c83073e9c6438 100644 --- a/cmd/manager_logging.go +++ b/cmd/manager_logging.go @@ -196,10 +196,7 @@ var ( } ) -func runRemoveLogger(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runRemoveLogger(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) logger := c.String("logger") if len(logger) == 0 { @@ -211,10 +208,7 @@ func runRemoveLogger(_ context.Context, c *cli.Command) error { return handleCliResponseExtra(extra) } -func runAddConnLogger(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runAddConnLogger(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) vals := map[string]any{} mode := "conn" @@ -238,13 +232,10 @@ func runAddConnLogger(_ context.Context, c *cli.Command) error { if c.IsSet("reconnect-on-message") { vals["reconnectOnMsg"] = c.Bool("reconnect-on-message") } - return commonAddLogger(c, mode, vals) + return commonAddLogger(ctx, c, mode, vals) } -func runAddFileLogger(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runAddFileLogger(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) vals := map[string]any{} mode := "file" @@ -271,10 +262,10 @@ func runAddFileLogger(_ context.Context, c *cli.Command) error { if c.IsSet("compression-level") { vals["compressionLevel"] = c.Int("compression-level") } - return commonAddLogger(c, mode, vals) + return commonAddLogger(ctx, c, mode, vals) } -func commonAddLogger(c *cli.Command, mode string, vals map[string]any) error { +func commonAddLogger(ctx context.Context, c *cli.Command, mode string, vals map[string]any) error { if len(c.String("level")) > 0 { vals["level"] = log.LevelFromString(c.String("level")).String() } @@ -301,46 +292,33 @@ func commonAddLogger(c *cli.Command, mode string, vals map[string]any) error { if c.IsSet("writer") { writer = c.String("writer") } - ctx, cancel := installSignals() - defer cancel() extra := private.AddLogger(ctx, logger, writer, mode, vals) return handleCliResponseExtra(extra) } -func runPauseLogging(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runPauseLogging(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) userMsg := private.PauseLogging(ctx) _, _ = fmt.Fprintln(os.Stdout, userMsg) return nil } -func runResumeLogging(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runResumeLogging(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) userMsg := private.ResumeLogging(ctx) _, _ = fmt.Fprintln(os.Stdout, userMsg) return nil } -func runReleaseReopenLogging(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runReleaseReopenLogging(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) userMsg := private.ReleaseReopenLogging(ctx) _, _ = fmt.Fprintln(os.Stdout, userMsg) return nil } -func runSetLogSQL(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() +func runSetLogSQL(ctx context.Context, c *cli.Command) error { setup(ctx, c.Bool("debug")) extra := private.SetLogSQL(ctx, !c.Bool("off")) diff --git a/cmd/migrate.go b/cmd/migrate.go index 8b7653a980fc5..e24dc9e5720f7 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -22,11 +22,8 @@ var CmdMigrate = &cli.Command{ Action: runMigrate, } -func runMigrate(_ context.Context, c *cli.Command) error { - stdCtx, cancel := installSignals() - defer cancel() - - if err := initDB(stdCtx); err != nil { +func runMigrate(ctx context.Context, c *cli.Command) error { + if err := initDB(ctx); err != nil { return err } diff --git a/cmd/migrate_storage.go b/cmd/migrate_storage.go index af83aee80cb0a..2c63e15f509c6 100644 --- a/cmd/migrate_storage.go +++ b/cmd/migrate_storage.go @@ -213,11 +213,8 @@ func migrateActionsArtifacts(ctx context.Context, dstStorage storage.ObjectStora }) } -func runMigrateStorage(_ context.Context, cmd *cli.Command) error { - stdCtx, cancel := installSignals() - defer cancel() - - if err := initDB(stdCtx); err != nil { +func runMigrateStorage(ctx context.Context, cmd *cli.Command) error { + if err := initDB(ctx); err != nil { return err } @@ -248,13 +245,13 @@ func runMigrateStorage(_ context.Context, cmd *cli.Command) error { return nil } dstStorage, err = storage.NewLocalStorage( - stdCtx, + ctx, &setting.Storage{ Path: p, }) case string(setting.MinioStorageType): dstStorage, err = storage.NewMinioStorage( - stdCtx, + ctx, &setting.Storage{ MinioConfig: setting.MinioStorageConfig{ Endpoint: cmd.String("minio-endpoint"), @@ -271,7 +268,7 @@ func runMigrateStorage(_ context.Context, cmd *cli.Command) error { }) case string(setting.AzureBlobStorageType): dstStorage, err = storage.NewAzureBlobStorage( - stdCtx, + ctx, &setting.Storage{ AzureBlobConfig: setting.AzureBlobStorageConfig{ Endpoint: cmd.String("azureblob-endpoint"), @@ -301,7 +298,7 @@ func runMigrateStorage(_ context.Context, cmd *cli.Command) error { tp := strings.ToLower(cmd.String("type")) if m, ok := migratedMethods[tp]; ok { - if err := m(stdCtx, dstStorage); err != nil { + if err := m(ctx, dstStorage); err != nil { return err } log.Info("%s files have successfully been copied to the new storage.", tp) diff --git a/cmd/restore_repo.go b/cmd/restore_repo.go index 238789615fddb..c61f5a582efe4 100644 --- a/cmd/restore_repo.go +++ b/cmd/restore_repo.go @@ -49,10 +49,7 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme }, } -func runRestoreRepository(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runRestoreRepository(ctx context.Context, c *cli.Command) error { setting.MustInstalled() var units []string if s := c.String("units"); s != "" { diff --git a/cmd/serv.go b/cmd/serv.go index 9f5665c66833b..e4434450d6a1f 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -152,10 +152,7 @@ func getLFSAuthToken(ctx context.Context, lfsVerb string, results *private.ServC return "Bearer " + tokenString, nil } -func runServ(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runServ(ctx context.Context, c *cli.Command) error { // FIXME: This needs to internationalised setup(ctx, c.Bool("debug")) diff --git a/contrib/backport/backport.go b/contrib/backport/backport.go index e0f21e451d6c8..9e7a78dab5344 100644 --- a/contrib/backport/backport.go +++ b/contrib/backport/backport.go @@ -105,16 +105,14 @@ OPTIONS: ` app.Action = runBackport - - if err := app.Run(context.Background(), os.Args); err != nil { + ctx, cancel := installSignals() + defer cancel() + if err := app.Run(ctx, os.Args); err != nil { fmt.Fprintf(os.Stderr, "Unable to backport: %v\n", err) } } -func runBackport(_ context.Context, c *cli.Command) error { - ctx, cancel := installSignals() - defer cancel() - +func runBackport(ctx context.Context, c *cli.Command) error { continuing := c.Bool("continue") var pr string From eba14089f6719a7bcaed028bf0a315af2eeeb100 Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Sun, 25 May 2025 00:16:48 +0200 Subject: [PATCH 08/18] add coverage for cert subcommand --- cmd/cert.go | 119 ++++++++++++++++++++++++++------------------ cmd/cert_test.go | 125 +++++++++++++++++++++++++++++++++++++++++++++++ cmd/main.go | 2 +- 3 files changed, 197 insertions(+), 49 deletions(-) create mode 100644 cmd/cert_test.go diff --git a/cmd/cert.go b/cmd/cert.go index 7ab2c843774d3..329a9a10ebc43 100644 --- a/cmd/cert.go +++ b/cmd/cert.go @@ -14,6 +14,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "fmt" "log" "math/big" "net" @@ -25,43 +26,55 @@ import ( ) // CmdCert represents the available cert sub-command. -var CmdCert = &cli.Command{ - Name: "cert", - Usage: "Generate self-signed certificate", - Description: `Generate a self-signed X.509 certificate for a TLS server. +func CmdCert() *cli.Command { + return &cli.Command{ + Name: "cert", + Usage: "Generate self-signed certificate", + Description: `Generate a self-signed X.509 certificate for a TLS server. Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`, - Action: runCert, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "host", - Value: "", - Usage: "Comma-separated hostnames and IPs to generate a certificate for", + Action: runCert, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "host", + Value: "", + Usage: "Comma-separated hostnames and IPs to generate a certificate for", + }, + &cli.StringFlag{ + Name: "ecdsa-curve", + Value: "", + Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521", + }, + &cli.IntFlag{ + Name: "rsa-bits", + Value: 3072, + Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set", + }, + &cli.StringFlag{ + Name: "start-date", + Value: "", + Usage: "Creation date formatted as Jan 1 15:04:05 2011", + }, + &cli.DurationFlag{ + Name: "duration", + Value: 365 * 24 * time.Hour, + Usage: "Duration that certificate is valid for", + }, + &cli.BoolFlag{ + Name: "ca", + Usage: "whether this cert should be its own Certificate Authority", + }, + &cli.StringFlag{ + Name: "out", + Value: "cert.pem", + Usage: "Path to the file where there certificate will be saved", + }, + &cli.StringFlag{ + Name: "keyout", + Value: "key.pem", + Usage: "Path to the file where there certificate key will be saved", + }, }, - &cli.StringFlag{ - Name: "ecdsa-curve", - Value: "", - Usage: "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521", - }, - &cli.IntFlag{ - Name: "rsa-bits", - Value: 3072, - Usage: "Size of RSA key to generate. Ignored if --ecdsa-curve is set", - }, - &cli.StringFlag{ - Name: "start-date", - Value: "", - Usage: "Creation date formatted as Jan 1 15:04:05 2011", - }, - &cli.DurationFlag{ - Name: "duration", - Value: 365 * 24 * time.Hour, - Usage: "Duration that certificate is valid for", - }, - &cli.BoolFlag{ - Name: "ca", - Usage: "whether this cert should be its own Certificate Authority", - }, - }, + } } func publicKey(priv any) any { @@ -109,17 +122,19 @@ func runCert(_ context.Context, c *cli.Command) error { case "P521": priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) default: - log.Fatalf("Unrecognized elliptic curve: %q", c.String("ecdsa-curve")) + err = fmt.Errorf("Unrecognized elliptic curve: %q", c.String("ecdsa-curve")) } if err != nil { - log.Fatalf("Failed to generate private key: %v", err) + // log.Fatalf("Failed to generate private key: %v", err) + return err } var notBefore time.Time if startDate := c.String("start-date"); startDate != "" { notBefore, err = time.Parse("Jan 2 15:04:05 2006", startDate) if err != nil { - log.Fatalf("Failed to parse creation date: %v", err) + // log.Fatalf("Failed to parse creation date: %v", err) + return fmt.Errorf("Failed to parse creation date %w", err) } } else { notBefore = time.Now() @@ -130,7 +145,8 @@ func runCert(_ context.Context, c *cli.Command) error { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { - log.Fatalf("Failed to generate serial number: %v", err) + // log.Fatalf("Failed to generate serial number: %v", err) + return err } template := x509.Certificate{ @@ -163,34 +179,41 @@ func runCert(_ context.Context, c *cli.Command) error { derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) if err != nil { - log.Fatalf("Failed to create certificate: %v", err) + // log.Fatalf("Failed to create certificate: %v", err) + return err } - certOut, err := os.Create("cert.pem") + certOut, err := os.Create(c.String("out")) if err != nil { - log.Fatalf("Failed to open cert.pem for writing: %v", err) + // log.Fatalf("Failed to open cert.pem for writing: %v", err) + return err } err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) if err != nil { - log.Fatalf("Failed to encode certificate: %v", err) + // log.Fatalf("Failed to encode certificate: %v", err) + return err } err = certOut.Close() if err != nil { - log.Fatalf("Failed to write cert: %v", err) + // log.Fatalf("Failed to write cert: %v", err) + return err } log.Println("Written cert.pem") - keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) + keyOut, err := os.OpenFile(c.String("keyout"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600) if err != nil { - log.Fatalf("Failed to open key.pem for writing: %v", err) + // log.Fatalf("Failed to open key.pem for writing: %v", err) + return err } err = pem.Encode(keyOut, pemBlockForKey(priv)) if err != nil { - log.Fatalf("Failed to encode key: %v", err) + // log.Fatalf("Failed to encode key: %v", err) + return err } err = keyOut.Close() if err != nil { - log.Fatalf("Failed to write key: %v", err) + // log.Fatalf("Failed to write key: %v", err) + return err } log.Println("Written key.pem") return nil diff --git a/cmd/cert_test.go b/cmd/cert_test.go new file mode 100644 index 0000000000000..062662a28ed30 --- /dev/null +++ b/cmd/cert_test.go @@ -0,0 +1,125 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCertCommand(t *testing.T) { + cases := []struct { + name string + args []string + }{ + { + name: "RSA cert generation", + args: []string{ + "cert-test", + "--host", "localhost", + "--rsa-bits", "2048", + "--duration", "1h", + "--start-date", "Jan 1 00:00:00 2024", + }, + }, + { + name: "ECDSA cert generation", + args: []string{ + "cert-test", + "--host", "localhost", + "--ecdsa-curve", "P256", + "--duration", "1h", + "--start-date", "Jan 1 00:00:00 2024", + }, + }, + { + name: "mixed host, certificate authority", + args: []string{ + "cert-test", + "--host", "localhost,127.0.0.1", + "--duration", "1h", + "--start-date", "Jan 1 00:00:00 2024", + }, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + app := CmdCert() + tempDir := t.TempDir() + + certFile := filepath.Join(tempDir, "cert.pem") + keyFile := filepath.Join(tempDir, "key.pem") + + args := append(c.args, "--out", certFile, "--keyout", keyFile) + err := app.Run(t.Context(), args) + require.NoError(t, err) + + assert.FileExists(t, certFile) + assert.FileExists(t, keyFile) + }) + } +} + +func TestCertCommandFailures(t *testing.T) { + cases := []struct { + name string + args []string + errMsg string + }{ + { + name: "Start Date Parsing failure", + args: []string{ + "cert-test", + "--host", "localhost", + "--start-date", "invalid-date", + }, + errMsg: "parsing time", + }, + { + name: "Unknown curve", + args: []string{ + "cert-test", + "--host", "localhost", + "--ecdsa-curve", "invalid-curve", + }, + errMsg: "Unrecognized elliptic curve", + }, + { + name: "Key generation failure", + args: []string{ + "cert-test", + "--host", "localhost", + "--rsa-bits", "invalid-bits", + }, + }, + { + name: "Missing parameters", + args: []string{ + "cert-test", + }, + errMsg: "host is not set", + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + app := CmdCert() + tempDir := t.TempDir() + + certFile := filepath.Join(tempDir, "cert.pem") + keyFile := filepath.Join(tempDir, "key.pem") + args := append(c.args, "--out", certFile, "--keyout", keyFile) + err := app.Run(t.Context(), args) + require.Error(t, err) + if c.errMsg != "" { + assert.ErrorContains(t, err, c.errMsg) + } + assert.NoFileExists(t, certFile) + assert.NoFileExists(t, keyFile) + }) + } +} diff --git a/cmd/main.go b/cmd/main.go index d086fda275fe8..c73f8645bfcc1 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -150,7 +150,7 @@ func NewMainApp(appVer AppVersion) *cli.Command { // these sub-commands do not need the config file, and they do not depend on any path or environment variable. subCmdStandalone := []*cli.Command{ - CmdCert, + CmdCert(), CmdGenerate, CmdDocs, } From 173ad6c9dab3bb4af0b7a21e57179e21753588b9 Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Sun, 25 May 2025 18:57:49 +0200 Subject: [PATCH 09/18] add coverage for admin user delete command --- cmd/admin_user.go | 2 +- cmd/admin_user_delete.go | 56 +++++++++-------- cmd/admin_user_delete_test.go | 113 ++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 27 deletions(-) create mode 100644 cmd/admin_user_delete_test.go diff --git a/cmd/admin_user.go b/cmd/admin_user.go index 64213d59ea388..a16cdd6012387 100644 --- a/cmd/admin_user.go +++ b/cmd/admin_user.go @@ -14,7 +14,7 @@ var subcmdUser = &cli.Command{ microcmdUserCreate(), microcmdUserList, microcmdUserChangePassword, - microcmdUserDelete, + microcmdUserDelete(), microcmdUserGenerateAccessToken, microcmdUserMustChangePassword, }, diff --git a/cmd/admin_user_delete.go b/cmd/admin_user_delete.go index 6fe1213d41b61..e56e895767764 100644 --- a/cmd/admin_user_delete.go +++ b/cmd/admin_user_delete.go @@ -10,45 +10,49 @@ import ( "strings" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" user_service "code.gitea.io/gitea/services/user" "github.com/urfave/cli/v3" ) -var microcmdUserDelete = &cli.Command{ - Name: "delete", - Usage: "Delete specific user by id, name or email", - Flags: []cli.Flag{ - &cli.Int64Flag{ - Name: "id", - Usage: "ID of user of the user to delete", +func microcmdUserDelete() *cli.Command { + return &cli.Command{ + Name: "delete", + Usage: "Delete specific user by id, name or email", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "id", + Usage: "ID of user of the user to delete", + }, + &cli.StringFlag{ + Name: "username", + Aliases: []string{"u"}, + Usage: "Username of the user to delete", + }, + &cli.StringFlag{ + Name: "email", + Aliases: []string{"e"}, + Usage: "Email of the user to delete", + }, + &cli.BoolFlag{ + Name: "purge", + Usage: "Purge user, all their repositories, organizations and comments", + }, }, - &cli.StringFlag{ - Name: "username", - Aliases: []string{"u"}, - Usage: "Username of the user to delete", - }, - &cli.StringFlag{ - Name: "email", - Aliases: []string{"e"}, - Usage: "Email of the user to delete", - }, - &cli.BoolFlag{ - Name: "purge", - Usage: "Purge user, all their repositories, organizations and comments", - }, - }, - Action: runDeleteUser, + Action: runDeleteUser, + } } - func runDeleteUser(ctx context.Context, c *cli.Command) error { if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") { return errors.New("You must provide the id, username or email of a user to delete") } - if err := initDB(ctx); err != nil { - return err + if !setting.IsInTesting { + if err := initDB(ctx); err != nil { + return err + } } if err := storage.Init(); err != nil { diff --git a/cmd/admin_user_delete_test.go b/cmd/admin_user_delete_test.go new file mode 100644 index 0000000000000..b983b14d17aa3 --- /dev/null +++ b/cmd/admin_user_delete_test.go @@ -0,0 +1,113 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "fmt" + "strings" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + + "github.com/stretchr/testify/require" +) + +func TestAdminUserDelete(t *testing.T) { + ctx := t.Context() + defer func() { + require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) + require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{})) + require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{})) + }() + + setupTestUser := func(t *testing.T) { + unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) + err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"}) + require.NoError(t, err) + } + + t.Run("delete user by id", func(t *testing.T) { + setupTestUser(t) + + u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--id", fmt.Sprintf("%d", u.ID)}) + require.NoError(t, err) + unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) + }) + t.Run("delete user by username", func(t *testing.T) { + setupTestUser(t) + + err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--username", "testuser"}) + require.NoError(t, err) + unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) + + }) + t.Run("delete user by email", func(t *testing.T) { + setupTestUser(t) + + err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--email", "testuser@gitea.local"}) + require.NoError(t, err) + unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) + }) + t.Run("delete user by all 3 attributes", func(t *testing.T) { + setupTestUser(t) + + u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + err := microcmdUserDelete().Run(ctx, []string{"delete", "--id", fmt.Sprintf("%d", u.ID), "--username", "testuser", "--email", "testuser@gitea.local"}) + require.NoError(t, err) + unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) + }) +} + +func TestAdminUserDeleteFailure(t *testing.T) { + testCases := []struct { + name string + args []string + expectedErr string + }{ + { + name: "no user to delete", + args: []string{"delete", "--username", "nonexistentuser"}, + expectedErr: "user does not exist", + }, + { + name: "user exists but provided username does not match", + args: []string{"delete", "--email", "testuser@gitea.local", "--username", "wrongusername"}, + expectedErr: "The user testuser who has email testuser@gitea.local does not match the provided username wrongusername", + }, + { + name: "user exists but provided id does not match", + args: []string{"delete", "--username", "testuser", "--id", "999"}, + expectedErr: "The user testuser does not match the provided id 999", + }, + { + name: "no required flags are provided", + args: []string{"delete"}, + expectedErr: "You must provide the id, username or email of a user to delete", + }, + } + + for _, tc := range testCases { + + t.Run(tc.name, func(t *testing.T) { + ctx := t.Context() + if strings.Contains(tc.name, "user exists") { + unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) + err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"}) + require.NoError(t, err) + } + + err := microcmdUserDelete().Run(ctx, tc.args) + require.Error(t, err) + require.Contains(t, err.Error(), tc.expectedErr) + }) + + require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) + require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{})) + require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{})) + } +} From 13b9d6e4dbc9de4b1b80c20e2a2ee952e3f45ea1 Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Sun, 25 May 2025 20:10:01 +0200 Subject: [PATCH 10/18] add coverage for change password --- cmd/admin_user.go | 2 +- cmd/admin_user_change_password.go | 52 ++++++++------- cmd/admin_user_change_password_test.go | 90 ++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 25 deletions(-) create mode 100644 cmd/admin_user_change_password_test.go diff --git a/cmd/admin_user.go b/cmd/admin_user.go index a16cdd6012387..0df63daf27d08 100644 --- a/cmd/admin_user.go +++ b/cmd/admin_user.go @@ -13,7 +13,7 @@ var subcmdUser = &cli.Command{ Commands: []*cli.Command{ microcmdUserCreate(), microcmdUserList, - microcmdUserChangePassword, + microcmdUserChangePassword(), microcmdUserDelete(), microcmdUserGenerateAccessToken, microcmdUserMustChangePassword, diff --git a/cmd/admin_user_change_password.go b/cmd/admin_user_change_password.go index 3c4357ec0a2d8..aef6b9f944a03 100644 --- a/cmd/admin_user_change_password.go +++ b/cmd/admin_user_change_password.go @@ -17,29 +17,31 @@ import ( "github.com/urfave/cli/v3" ) -var microcmdUserChangePassword = &cli.Command{ - Name: "change-password", - Usage: "Change a user's password", - Action: runChangePassword, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "username", - Aliases: []string{"u"}, - Value: "", - Usage: "The user to change password for", +func microcmdUserChangePassword() *cli.Command { + return &cli.Command{ + Name: "change-password", + Usage: "Change a user's password", + Action: runChangePassword, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "username", + Aliases: []string{"u"}, + Value: "", + Usage: "The user to change password for", + }, + &cli.StringFlag{ + Name: "password", + Aliases: []string{"p"}, + Value: "", + Usage: "New password to set for user", + }, + &cli.BoolFlag{ + Name: "must-change-password", + Usage: "User must change password (can be disabled by --must-change-password=false)", + Value: true, + }, }, - &cli.StringFlag{ - Name: "password", - Aliases: []string{"p"}, - Value: "", - Usage: "New password to set for user", - }, - &cli.BoolFlag{ - Name: "must-change-password", - Usage: "User must change password (can be disabled by --must-change-password=false)", - Value: true, - }, - }, + } } func runChangePassword(ctx context.Context, c *cli.Command) error { @@ -47,8 +49,10 @@ func runChangePassword(ctx context.Context, c *cli.Command) error { return err } - if err := initDB(ctx); err != nil { - return err + if !setting.IsInTesting { + if err := initDB(ctx); err != nil { + return err + } } user, err := user_model.GetUserByName(ctx, c.String("username")) diff --git a/cmd/admin_user_change_password_test.go b/cmd/admin_user_change_password_test.go new file mode 100644 index 0000000000000..a5aadd62000b1 --- /dev/null +++ b/cmd/admin_user_change_password_test.go @@ -0,0 +1,90 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestChangePasswordCommand(t *testing.T) { + ctx := t.Context() + + defer func() { + require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) + }() + + t.Run("change password successfully", func(t *testing.T) { + // defer func() { + // require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) + // }() + // Prepare test user + unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) + err := microcmdUserCreate().Run(ctx, []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"}) + require.NoError(t, err) + + // load test user + userBase := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + + // Change the password + err = microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "newpassword"}) + require.NoError(t, err) + + // Verify the password has been changed + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.NotEqual(t, userBase.Passwd, user.Passwd) + assert.NotEqual(t, userBase.Salt, user.Salt) + + // Additional check for must-change-password flag + require.NoError(t, microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "anotherpassword", "--must-change-password=false"})) + user = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.False(t, user.MustChangePassword) + + require.NoError(t, microcmdUserChangePassword().Run(ctx, []string{"change-password", "--username", "testuser", "--password", "yetanotherpassword", "--must-change-password"})) + user = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.True(t, user.MustChangePassword) + }) + + t.Run("failure cases", func(t *testing.T) { + testCases := []struct { + name string + args []string + expectedErr string + }{ + { + name: "user does not exist", + args: []string{"change-password", "--username", "nonexistentuser", "--password", "newpassword"}, + expectedErr: "user does not exist", + }, + { + name: "missing username", + args: []string{"change-password", "--password", "newpassword"}, + expectedErr: "username is not set", + }, + { + name: "missing password", + args: []string{"change-password", "--username", "testuser"}, + expectedErr: "password is not set", + }, + { + name: "too short password", + args: []string{"change-password", "--username", "testuser", "--password", "1"}, + expectedErr: "password is not long enough", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := microcmdUserChangePassword().Run(ctx, tc.args) + require.Error(t, err) + require.Contains(t, err.Error(), tc.expectedErr) + }) + } + }) +} From 02497c015339bd5e33e43bc11a95e1670149910d Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Sun, 25 May 2025 20:58:33 +0200 Subject: [PATCH 11/18] add coverage for must change password --- cmd/admin_user.go | 2 +- cmd/admin_user_must_change_password.go | 47 +++++++------ cmd/admin_user_must_change_password_test.go | 77 +++++++++++++++++++++ 3 files changed, 104 insertions(+), 22 deletions(-) create mode 100644 cmd/admin_user_must_change_password_test.go diff --git a/cmd/admin_user.go b/cmd/admin_user.go index 0df63daf27d08..3a24c3e56f191 100644 --- a/cmd/admin_user.go +++ b/cmd/admin_user.go @@ -16,6 +16,6 @@ var subcmdUser = &cli.Command{ microcmdUserChangePassword(), microcmdUserDelete(), microcmdUserGenerateAccessToken, - microcmdUserMustChangePassword, + microcmdUserMustChangePassword(), }, } diff --git a/cmd/admin_user_must_change_password.go b/cmd/admin_user_must_change_password.go index 677477ca92d75..8521853dc19da 100644 --- a/cmd/admin_user_must_change_password.go +++ b/cmd/admin_user_must_change_password.go @@ -9,30 +9,33 @@ import ( "fmt" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" "github.com/urfave/cli/v3" ) -var microcmdUserMustChangePassword = &cli.Command{ - Name: "must-change-password", - Usage: "Set the must change password flag for the provided users or all users", - Action: runMustChangePassword, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "all", - Aliases: []string{"A"}, - Usage: "All users must change password, except those explicitly excluded with --exclude", +func microcmdUserMustChangePassword() *cli.Command { + return &cli.Command{ + Name: "must-change-password", + Usage: "Set the must change password flag for the provided users or all users", + Action: runMustChangePassword, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "all", + Aliases: []string{"A"}, + Usage: "All users must change password, except those explicitly excluded with --exclude", + }, + &cli.StringSliceFlag{ + Name: "exclude", + Aliases: []string{"e"}, + Usage: "Do not change the must-change-password flag for these users", + }, + &cli.BoolFlag{ + Name: "unset", + Usage: "Instead of setting the must-change-password flag, unset it", + }, }, - &cli.StringSliceFlag{ - Name: "exclude", - Aliases: []string{"e"}, - Usage: "Do not change the must-change-password flag for these users", - }, - &cli.BoolFlag{ - Name: "unset", - Usage: "Instead of setting the must-change-password flag, unset it", - }, - }, + } } func runMustChangePassword(ctx context.Context, c *cli.Command) error { @@ -44,8 +47,10 @@ func runMustChangePassword(ctx context.Context, c *cli.Command) error { all := c.Bool("all") exclude := c.StringSlice("exclude") - if err := initDB(ctx); err != nil { - return err + if !setting.IsInTesting { + if err := initDB(ctx); err != nil { + return err + } } n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args().Slice(), exclude) diff --git a/cmd/admin_user_must_change_password_test.go b/cmd/admin_user_must_change_password_test.go new file mode 100644 index 0000000000000..9b1d822801e80 --- /dev/null +++ b/cmd/admin_user_must_change_password_test.go @@ -0,0 +1,77 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMustChangePassword(t *testing.T) { + defer func() { + require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) + }() + err := microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuser", "--email", "testuser@gitea.local", "--random-password"}) + require.NoError(t, err) + err = microcmdUserCreate().Run(t.Context(), []string{"create", "--username", "testuserexclude", "--email", "testuserexclude@gitea.local", "--random-password"}) + require.NoError(t, err) + // Reset password change flag + err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset"}) + require.NoError(t, err) + + testUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.False(t, testUser.MustChangePassword) + testUserExclude := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) + assert.False(t, testUserExclude.MustChangePassword) + + // Make all users change password + err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all"}) + require.NoError(t, err) + + testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.True(t, testUser.MustChangePassword) + testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) + assert.True(t, testUserExclude.MustChangePassword) + + // Reset password change flag but exclude all tested users + err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--unset", "--exclude", "testuser,testuserexclude"}) + require.NoError(t, err) + + testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.True(t, testUser.MustChangePassword) + testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) + assert.True(t, testUserExclude.MustChangePassword) + + // Reset password change flag by listing multiple users + err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser", "testuserexclude"}) + require.NoError(t, err) + + testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.False(t, testUser.MustChangePassword) + testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) + assert.False(t, testUserExclude.MustChangePassword) + + // Exclude a user from all user + err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--all", "--exclude", "testuserexclude"}) + require.NoError(t, err) + + testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.True(t, testUser.MustChangePassword) + testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) + assert.False(t, testUserExclude.MustChangePassword) + + // Unset a flag for single user + err = microcmdUserMustChangePassword().Run(t.Context(), []string{"change-test", "--unset", "testuser"}) + require.NoError(t, err) + + testUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) + assert.False(t, testUser.MustChangePassword) + testUserExclude = unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuserexclude"}) + assert.False(t, testUserExclude.MustChangePassword) +} From 8e2333f46dbaf2e17b696a777776c9ae8e6c3d4e Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Sun, 25 May 2025 21:14:25 +0200 Subject: [PATCH 12/18] drop argsSet in favor of required --- cmd/admin.go | 6 +++--- cmd/admin_user_change_password.go | 20 ++++++++------------ cmd/admin_user_change_password_test.go | 4 ++-- cmd/admin_user_create.go | 9 +++------ cmd/cert.go | 10 +++------- cmd/cert_test.go | 2 +- cmd/cmd.go | 16 ---------------- cmd/mailer.go | 4 ---- 8 files changed, 20 insertions(+), 51 deletions(-) diff --git a/cmd/admin.go b/cmd/admin.go index c38357625e9ef..3e998a8989ea4 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -70,9 +70,9 @@ var ( Action: runSendMail, Flags: []cli.Flag{ &cli.StringFlag{ - Name: "title", - Usage: `a title of a message`, - Value: "", + Name: "title", + Usage: "a title of a message", + Required: true, }, &cli.StringFlag{ Name: "content", diff --git a/cmd/admin_user_change_password.go b/cmd/admin_user_change_password.go index aef6b9f944a03..c27905b4db5e7 100644 --- a/cmd/admin_user_change_password.go +++ b/cmd/admin_user_change_password.go @@ -24,16 +24,16 @@ func microcmdUserChangePassword() *cli.Command { Action: runChangePassword, Flags: []cli.Flag{ &cli.StringFlag{ - Name: "username", - Aliases: []string{"u"}, - Value: "", - Usage: "The user to change password for", + Name: "username", + Aliases: []string{"u"}, + Usage: "The user to change password for", + Required: true, }, &cli.StringFlag{ - Name: "password", - Aliases: []string{"p"}, - Value: "", - Usage: "New password to set for user", + Name: "password", + Aliases: []string{"p"}, + Usage: "New password to set for user", + Required: true, }, &cli.BoolFlag{ Name: "must-change-password", @@ -45,10 +45,6 @@ func microcmdUserChangePassword() *cli.Command { } func runChangePassword(ctx context.Context, c *cli.Command) error { - if err := argsSet(c, "username", "password"); err != nil { - return err - } - if !setting.IsInTesting { if err := initDB(ctx); err != nil { return err diff --git a/cmd/admin_user_change_password_test.go b/cmd/admin_user_change_password_test.go index a5aadd62000b1..55a59e21453b3 100644 --- a/cmd/admin_user_change_password_test.go +++ b/cmd/admin_user_change_password_test.go @@ -65,12 +65,12 @@ func TestChangePasswordCommand(t *testing.T) { { name: "missing username", args: []string{"change-password", "--password", "newpassword"}, - expectedErr: "username is not set", + expectedErr: `"username" not set`, }, { name: "missing password", args: []string{"change-password", "--username", "testuser"}, - expectedErr: "password is not set", + expectedErr: `"password" not set`, }, { name: "too short password", diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 4f68c932e0946..3520f77e14f0e 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -52,8 +52,9 @@ func microcmdUserCreate() *cli.Command { Usage: "User password", }, &cli.StringFlag{ - Name: "email", - Usage: "User email address", + Name: "email", + Usage: "User email address", + Required: true, }, &cli.BoolFlag{ Name: "admin", @@ -104,10 +105,6 @@ func runCreateUser(ctx context.Context, c *cli.Command) error { // duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future. setting.LoadSettings() - if err := argsSet(c, "email"); err != nil { - return err - } - userTypes := map[string]user_model.UserType{ "individual": user_model.UserTypeIndividual, "bot": user_model.UserTypeBot, diff --git a/cmd/cert.go b/cmd/cert.go index 329a9a10ebc43..fd68b2539c543 100644 --- a/cmd/cert.go +++ b/cmd/cert.go @@ -35,9 +35,9 @@ Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`, Action: runCert, Flags: []cli.Flag{ &cli.StringFlag{ - Name: "host", - Value: "", - Usage: "Comma-separated hostnames and IPs to generate a certificate for", + Name: "host", + Usage: "Comma-separated hostnames and IPs to generate a certificate for", + Required: true, }, &cli.StringFlag{ Name: "ecdsa-curve", @@ -104,10 +104,6 @@ func pemBlockForKey(priv any) *pem.Block { } func runCert(_ context.Context, c *cli.Command) error { - if err := argsSet(c, "host"); err != nil { - return err - } - var priv any var err error switch c.String("ecdsa-curve") { diff --git a/cmd/cert_test.go b/cmd/cert_test.go index 062662a28ed30..fbf5ca20730e7 100644 --- a/cmd/cert_test.go +++ b/cmd/cert_test.go @@ -102,7 +102,7 @@ func TestCertCommandFailures(t *testing.T) { args: []string{ "cert-test", }, - errMsg: "host is not set", + errMsg: `"host" not set`, }, } for _, c := range cases { diff --git a/cmd/cmd.go b/cmd/cmd.go index afd9b3124e83b..764cccccacaa8 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -18,26 +18,10 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "github.com/urfave/cli/v3" ) -// argsSet checks that all the required arguments are set. args is a list of -// arguments that must be set in the passed Context. -func argsSet(c *cli.Command, args ...string) error { - for _, a := range args { - if !c.IsSet(a) { - return errors.New(a + " is not set") - } - - if util.IsEmptyString(c.String(a)) { - return errors.New(a + " is required") - } - } - return nil -} - // confirm waits for user input which confirms an action func confirm() (bool, error) { var response string diff --git a/cmd/mailer.go b/cmd/mailer.go index e0bbd91ad665d..72bd8e56012ed 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -16,10 +16,6 @@ import ( func runSendMail(ctx context.Context, c *cli.Command) error { setting.MustInstalled() - if err := argsSet(c, "title"); err != nil { - return err - } - subject := c.String("title") confirmSkiped := c.Bool("force") body := c.String("content") From 23dd90f41d90a2d161f76c7c7923be6a3fa62041 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 4 Jun 2025 18:04:47 +0800 Subject: [PATCH 13/18] fix --- cmd/admin_user_change_password_test.go | 1 + cmd/admin_user_create.go | 2 +- cmd/admin_user_delete.go | 1 + cmd/admin_user_delete_test.go | 1 - cmd/admin_user_must_change_password_test.go | 1 + contrib/backport/backport.go | 28 +-------------------- 6 files changed, 5 insertions(+), 29 deletions(-) diff --git a/cmd/admin_user_change_password_test.go b/cmd/admin_user_change_password_test.go index 55a59e21453b3..17d0382af747a 100644 --- a/cmd/admin_user_change_password_test.go +++ b/cmd/admin_user_change_password_test.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 3520f77e14f0e..cbdb5f90e2e5a 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -135,7 +135,7 @@ func runCreateUser(ctx context.Context, c *cli.Command) error { } if !setting.IsInTesting { - // FIXME: need to refactor the "installSignals/initDB" related code later + // FIXME: need to refactor the "initDB" related code later // it doesn't make sense to call it in (almost) every command action function if err := initDB(ctx); err != nil { return err diff --git a/cmd/admin_user_delete.go b/cmd/admin_user_delete.go index e56e895767764..2504871e613ce 100644 --- a/cmd/admin_user_delete.go +++ b/cmd/admin_user_delete.go @@ -44,6 +44,7 @@ func microcmdUserDelete() *cli.Command { Action: runDeleteUser, } } + func runDeleteUser(ctx context.Context, c *cli.Command) error { if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") { return errors.New("You must provide the id, username or email of a user to delete") diff --git a/cmd/admin_user_delete_test.go b/cmd/admin_user_delete_test.go index b983b14d17aa3..92898d4bea89f 100644 --- a/cmd/admin_user_delete_test.go +++ b/cmd/admin_user_delete_test.go @@ -44,7 +44,6 @@ func TestAdminUserDelete(t *testing.T) { err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--username", "testuser"}) require.NoError(t, err) unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) - }) t.Run("delete user by email", func(t *testing.T) { setupTestUser(t) diff --git a/cmd/admin_user_must_change_password_test.go b/cmd/admin_user_must_change_password_test.go index 9b1d822801e80..a6611fdc041ab 100644 --- a/cmd/admin_user_must_change_password_test.go +++ b/cmd/admin_user_must_change_password_test.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/contrib/backport/backport.go b/contrib/backport/backport.go index 9e7a78dab5344..630bd775313f5 100644 --- a/contrib/backport/backport.go +++ b/contrib/backport/backport.go @@ -12,11 +12,9 @@ import ( "net/http" "os" "os/exec" - "os/signal" "path" "strconv" "strings" - "syscall" "github.com/google/go-github/v71/github" "github.com/urfave/cli/v3" @@ -105,9 +103,7 @@ OPTIONS: ` app.Action = runBackport - ctx, cancel := installSignals() - defer cancel() - if err := app.Run(ctx, os.Args); err != nil { + if err := app.Run(context.Background(), os.Args); err != nil { fmt.Fprintf(os.Stderr, "Unable to backport: %v\n", err) } } @@ -458,25 +454,3 @@ func determineSHAforPR(ctx context.Context, prStr, accessToken string) (string, return "", nil } - -func installSignals() (context.Context, context.CancelFunc) { - ctx, cancel := context.WithCancel(context.Background()) - go func() { - // install notify - signalChannel := make(chan os.Signal, 1) - - signal.Notify( - signalChannel, - syscall.SIGINT, - syscall.SIGTERM, - ) - select { - case <-signalChannel: - case <-ctx.Done(): - } - cancel() - signal.Reset() - }() - - return ctx, cancel -} From dfd47ce89349f8cbcfa11864a2e6182634c82391 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 4 Jun 2025 18:31:11 +0800 Subject: [PATCH 14/18] fix --- cmd/admin_auth_smtp.go | 2 -- cmd/main.go | 11 +++++------ cmd/main_test.go | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/cmd/admin_auth_smtp.go b/cmd/admin_auth_smtp.go index 457aa2f5c0405..1fee595d2d162 100644 --- a/cmd/admin_auth_smtp.go +++ b/cmd/admin_auth_smtp.go @@ -6,7 +6,6 @@ package cmd import ( "context" "errors" - "fmt" "strings" auth_model "code.gitea.io/gitea/models/auth" @@ -145,7 +144,6 @@ func (a *authService) runAddSMTP(ctx context.Context, c *cli.Command) error { } active := true if c.IsSet("active") { - fmt.Println("Active is set!", c.Bool("active")) active = c.Bool("active") } diff --git a/cmd/main.go b/cmd/main.go index c73f8645bfcc1..9b889273787bc 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -75,15 +75,15 @@ func appGlobalFlags() []cli.Flag { } } -func prepareSubcommandWithConfig(command *cli.Command, globalFlags []cli.Flag) { - command.Flags = append(append([]cli.Flag{}, globalFlags...), command.Flags...) +func prepareSubcommandWithGlobalFlags(command *cli.Command) { + command.Flags = append(append([]cli.Flag{}, appGlobalFlags()...), command.Flags...) command.Action = prepareWorkPathAndCustomConf(command.Action) command.HideHelp = true if command.Name != "help" { command.Commands = append(command.Commands, cmdHelp()) } for i := range command.Commands { - prepareSubcommandWithConfig(command.Commands[i], globalFlags) + prepareSubcommandWithGlobalFlags(command.Commands[i]) } } @@ -157,13 +157,12 @@ func NewMainApp(appVer AppVersion) *cli.Command { app.DefaultCommand = CmdWeb.Name - globalFlags := appGlobalFlags() app.Flags = append(app.Flags, cli.VersionFlag) - app.Flags = append(app.Flags, globalFlags...) + app.Flags = append(app.Flags, appGlobalFlags()...) app.HideHelp = true // use our own help action to show helps (with more information like default config) app.Before = PrepareConsoleLoggerLevel(log.INFO) for i := range subCmdWithConfig { - prepareSubcommandWithConfig(subCmdWithConfig[i], globalFlags) + prepareSubcommandWithGlobalFlags(subCmdWithConfig[i]) } app.Commands = append(app.Commands, subCmdWithConfig...) app.Commands = append(app.Commands, subCmdStandalone...) diff --git a/cmd/main_test.go b/cmd/main_test.go index 96b1830a31b6c..76dabb900ba5e 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -31,7 +31,7 @@ func makePathOutput(workPath, customPath, customConf string) string { func newTestApp(testCmdAction cli.ActionFunc) *cli.Command { app := NewMainApp(AppVersion{}) testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction} - prepareSubcommandWithConfig(testCmd, appGlobalFlags()) + prepareSubcommandWithGlobalFlags(testCmd, appGlobalFlags()) app.Commands = append(app.Commands, testCmd) app.DefaultCommand = testCmd.Name return app From cda23a7f0c9d30f7a751990a293777c7d2e7c3f4 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 4 Jun 2025 18:42:06 +0800 Subject: [PATCH 15/18] fix --- cmd/main.go | 1 - cmd/main_test.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 9b889273787bc..318860397b001 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -127,7 +127,6 @@ func NewMainApp(appVer AppVersion) *cli.Command { Version: appVer.Version + appVer.Extra, EnableShellCompletion: true, } - app.FullName() // these sub-commands need to use config file subCmdWithConfig := []*cli.Command{ diff --git a/cmd/main_test.go b/cmd/main_test.go index 76dabb900ba5e..7dfa87a0ef042 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -31,7 +31,7 @@ func makePathOutput(workPath, customPath, customConf string) string { func newTestApp(testCmdAction cli.ActionFunc) *cli.Command { app := NewMainApp(AppVersion{}) testCmd := &cli.Command{Name: "test-cmd", Action: testCmdAction} - prepareSubcommandWithGlobalFlags(testCmd, appGlobalFlags()) + prepareSubcommandWithGlobalFlags(testCmd) app.Commands = append(app.Commands, testCmd) app.DefaultCommand = testCmd.Name return app From c47d66698aedd4db943be681cfe12585f21cea03 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 4 Jun 2025 20:30:45 +0800 Subject: [PATCH 16/18] fix --- assets/go-licenses.json | 16 ++++++++-------- cmd/keys.go | 2 +- tests/integration/cmd_keys_test.go | 10 ++++++---- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/assets/go-licenses.json b/assets/go-licenses.json index 3827a092f145a..d961444239633 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -1080,9 +1080,14 @@ "licenseText": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that You\nchanged the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source form\nof the Work, excluding those notices that do not pertain to any part of the\nDerivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any\nDerivative Works that You distribute must include a readable copy of the\nattribution notices contained within such NOTICE file, excluding those notices\nthat do not pertain to any part of the Derivative Works, in at least one of the\nfollowing places: within a NOTICE text file distributed as part of the\nDerivative Works; within the Source form or documentation, if provided along\nwith the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents of\nthe NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works that\nYou distribute, alongside or as an addendum to the NOTICE text from the Work,\nprovided that such additional attribution notices cannot be construed as\nmodifying the License.\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction, or\ndistribution of Your modifications, or for any such Derivative Works as a whole,\nprovided Your use, reproduction, and distribution of the Work otherwise complies\nwith the conditions stated in this License.\n\n5. Submission of Contributions.\n\nUnless You explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the Work by You to the Licensor shall be under the terms and\nconditions of this License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify the terms of\nany separate license agreement you may have executed with Licensor regarding\nsuch Contributions.\n\n6. Trademarks.\n\nThis License does not grant permission to use the trade names, trademarks,\nservice marks, or product names of the Licensor, except as required for\nreasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty.\n\nUnless required by applicable law or agreed to in writing, Licensor provides the\nWork (and each Contributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,\nincluding, without limitation, any warranties or conditions of TITLE,\nNON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are\nsolely responsible for determining the appropriateness of using or\nredistributing the Work and assume any risks associated with Your exercise of\npermissions under this License.\n\n8. Limitation of Liability.\n\nIn no event and under no legal theory, whether in tort (including negligence),\ncontract, or otherwise, unless required by applicable law (such as deliberate\nand grossly negligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License or\nout of the use or inability to use the Work (including but not limited to\ndamages for loss of goodwill, work stoppage, computer failure or malfunction, or\nany and all other commercial damages or losses), even if such Contributor has\nbeen advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability.\n\nWhile redistributing the Work or Derivative Works thereof, You may choose to\noffer, and charge a fee for, acceptance of support, warranty, indemnity, or\nother liability obligations and/or rights consistent with this License. However,\nin accepting such obligations, You may act only on Your own behalf and on Your\nsole responsibility, not on behalf of any other Contributor, and only if You\nagree to indemnify, defend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason of your\naccepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"[]\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification within\nthird-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License." }, { - "name": "github.com/urfave/cli/v2", - "path": "github.com/urfave/cli/v2/LICENSE", - "licenseText": "MIT License\n\nCopyright (c) 2022 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" + "name": "github.com/urfave/cli-docs/v3", + "path": "github.com/urfave/cli-docs/v3/LICENSE", + "licenseText": "MIT License\n\nCopyright (c) 2023 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" + }, + { + "name": "github.com/urfave/cli/v3", + "path": "github.com/urfave/cli/v3/LICENSE", + "licenseText": "MIT License\n\nCopyright (c) 2023 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, { "name": "github.com/valyala/fastjson", @@ -1109,11 +1114,6 @@ "path": "github.com/xanzy/ssh-agent/LICENSE", "licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"{}\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n" }, - { - "name": "github.com/xrash/smetrics", - "path": "github.com/xrash/smetrics/LICENSE", - "licenseText": "Copyright (C) 2016 Felipe da Cunha Gonçalves\nAll Rights Reserved.\n\nMIT LICENSE\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" - }, { "name": "github.com/yohcop/openid-go", "path": "github.com/yohcop/openid-go/LICENSE", diff --git a/cmd/keys.go b/cmd/keys.go index 759932f1e9ee8..8710756a8136b 100644 --- a/cmd/keys.go +++ b/cmd/keys.go @@ -76,6 +76,6 @@ func runKeys(ctx context.Context, c *cli.Command) error { if extra.Error != nil { return extra.Error } - _, _ = fmt.Fprintln(c.Writer, strings.TrimSpace(authorizedString.Text)) + _, _ = fmt.Fprintln(c.Root().Writer, strings.TrimSpace(authorizedString.Text)) return nil } diff --git a/tests/integration/cmd_keys_test.go b/tests/integration/cmd_keys_test.go index daefed1a543a9..3878302ef0836 100644 --- a/tests/integration/cmd_keys_test.go +++ b/tests/integration/cmd_keys_test.go @@ -36,19 +36,21 @@ func Test_CmdKeys(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - out := new(bytes.Buffer) + var stdout, stderr bytes.Buffer app := &cli.Command{ - Writer: out, - Commands: []*cli.Command{cmd.CmdKeys}, + Writer: &stdout, + ErrWriter: &stderr, + Commands: []*cli.Command{cmd.CmdKeys}, } cmd.CmdKeys.HideHelp = true err := app.Run(t.Context(), append([]string{"prog"}, tt.args...)) if tt.wantErr { assert.Error(t, err) + assert.Equal(t, tt.expectedOutput, stderr.String()) } else { assert.NoError(t, err) + assert.Equal(t, tt.expectedOutput, stdout.String()) } - assert.Equal(t, tt.expectedOutput, out.String()) }) } }) From 94415506b431291fb4ed44c048777dcf447506cd Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 4 Jun 2025 20:36:24 +0800 Subject: [PATCH 17/18] fix name, fix lint --- cmd/admin.go | 8 ++++---- cmd/admin_auth_ldap.go | 8 ++++---- cmd/admin_auth_ldap_test.go | 8 ++++---- cmd/admin_user_delete_test.go | 7 +++---- cmd/cert.go | 4 ++-- cmd/cert_test.go | 10 ++++------ cmd/main.go | 2 +- 7 files changed, 22 insertions(+), 25 deletions(-) diff --git a/cmd/admin.go b/cmd/admin.go index da35f7101c61c..4e5f39bfe778c 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -53,10 +53,10 @@ var ( Commands: []*cli.Command{ microcmdAuthAddOauth(), microcmdAuthUpdateOauth(), - newMicrocmdAuthAddLdapBindDn(), - newMicrocmdAuthUpdateLdapBindDn(), - newMicrocmdAuthAddLdapSimpleAuth(), - newMicrocmdAuthUpdateLdapSimpleAuth(), + microcmdAuthAddLdapBindDn(), + microcmdAuthUpdateLdapBindDn(), + microcmdAuthAddLdapSimpleAuth(), + microcmdAuthUpdateLdapSimpleAuth(), microcmdAuthAddSMTP(), microcmdAuthUpdateSMTP(), microcmdAuthList, diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index 80786282e8df1..388a310d6c50e 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -24,7 +24,7 @@ type ( } ) -func newMicrocmdAuthAddLdapBindDn() *cli.Command { +func microcmdAuthAddLdapBindDn() *cli.Command { return &cli.Command{ Name: "add-ldap", Usage: "Add new LDAP (via Bind DN) authentication source", @@ -68,7 +68,7 @@ func newMicrocmdAuthAddLdapBindDn() *cli.Command { } } -func newMicrocmdAuthUpdateLdapBindDn() *cli.Command { +func microcmdAuthUpdateLdapBindDn() *cli.Command { return &cli.Command{ Name: "update-ldap", Usage: "Update existing LDAP (via Bind DN) authentication source", @@ -113,7 +113,7 @@ func newMicrocmdAuthUpdateLdapBindDn() *cli.Command { } } -func newMicrocmdAuthAddLdapSimpleAuth() *cli.Command { +func microcmdAuthAddLdapSimpleAuth() *cli.Command { return &cli.Command{ Name: "add-ldap-simple", Usage: "Add new LDAP (simple auth) authentication source", @@ -145,7 +145,7 @@ func newMicrocmdAuthAddLdapSimpleAuth() *cli.Command { } } -func newMicrocmdAuthUpdateLdapSimpleAuth() *cli.Command { +func microcmdAuthUpdateLdapSimpleAuth() *cli.Command { return &cli.Command{ Name: "update-ldap-simple", Usage: "Update existing LDAP (simple auth) authentication source", diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go index 36d98114dc0cb..3376b36eb9334 100644 --- a/cmd/admin_auth_ldap_test.go +++ b/cmd/admin_auth_ldap_test.go @@ -239,7 +239,7 @@ func TestAddLdapBindDn(t *testing.T) { // Create a copy of command to test app := cli.Command{ - Flags: newMicrocmdAuthAddLdapBindDn().Flags, + Flags: microcmdAuthAddLdapBindDn().Flags, Action: service.addLdapBindDn, } @@ -469,7 +469,7 @@ func TestAddLdapSimpleAuth(t *testing.T) { // Create a copy of command to test app := &cli.Command{ - Flags: newMicrocmdAuthAddLdapSimpleAuth().Flags, + Flags: microcmdAuthAddLdapSimpleAuth().Flags, Action: service.addLdapSimpleAuth, } @@ -945,7 +945,7 @@ func TestUpdateLdapBindDn(t *testing.T) { // Create a copy of command to test app := cli.Command{ - Flags: newMicrocmdAuthUpdateLdapBindDn().Flags, + Flags: microcmdAuthUpdateLdapBindDn().Flags, Action: service.updateLdapBindDn, } // Run it @@ -1333,7 +1333,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { // Create a copy of command to test app := cli.Command{ - Flags: newMicrocmdAuthUpdateLdapSimpleAuth().Flags, + Flags: microcmdAuthUpdateLdapSimpleAuth().Flags, Action: service.updateLdapSimpleAuth, } // Run it diff --git a/cmd/admin_user_delete_test.go b/cmd/admin_user_delete_test.go index 92898d4bea89f..5dd1400f72a55 100644 --- a/cmd/admin_user_delete_test.go +++ b/cmd/admin_user_delete_test.go @@ -4,7 +4,7 @@ package cmd import ( - "fmt" + "strconv" "strings" "testing" @@ -34,7 +34,7 @@ func TestAdminUserDelete(t *testing.T) { setupTestUser(t) u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) - err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--id", fmt.Sprintf("%d", u.ID)}) + err := microcmdUserDelete().Run(ctx, []string{"delete-test", "--id", strconv.FormatInt(u.ID, 10)}) require.NoError(t, err) unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) }) @@ -56,7 +56,7 @@ func TestAdminUserDelete(t *testing.T) { setupTestUser(t) u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "testuser"}) - err := microcmdUserDelete().Run(ctx, []string{"delete", "--id", fmt.Sprintf("%d", u.ID), "--username", "testuser", "--email", "testuser@gitea.local"}) + err := microcmdUserDelete().Run(ctx, []string{"delete", "--id", strconv.FormatInt(u.ID, 10), "--username", "testuser", "--email", "testuser@gitea.local"}) require.NoError(t, err) unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "testuser"}) }) @@ -91,7 +91,6 @@ func TestAdminUserDeleteFailure(t *testing.T) { } for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { ctx := t.Context() if strings.Contains(tc.name, "user exists") { diff --git a/cmd/cert.go b/cmd/cert.go index fd68b2539c543..2c4deef1f8502 100644 --- a/cmd/cert.go +++ b/cmd/cert.go @@ -25,8 +25,8 @@ import ( "github.com/urfave/cli/v3" ) -// CmdCert represents the available cert sub-command. -func CmdCert() *cli.Command { +// cmdCert represents the available cert sub-command. +func cmdCert() *cli.Command { return &cli.Command{ Name: "cert", Usage: "Generate self-signed certificate", diff --git a/cmd/cert_test.go b/cmd/cert_test.go index fbf5ca20730e7..9e55999b5e704 100644 --- a/cmd/cert_test.go +++ b/cmd/cert_test.go @@ -49,14 +49,13 @@ func TestCertCommand(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { - app := CmdCert() + app := cmdCert() tempDir := t.TempDir() certFile := filepath.Join(tempDir, "cert.pem") keyFile := filepath.Join(tempDir, "key.pem") - args := append(c.args, "--out", certFile, "--keyout", keyFile) - err := app.Run(t.Context(), args) + err := app.Run(t.Context(), append(c.args, "--out", certFile, "--keyout", keyFile)) require.NoError(t, err) assert.FileExists(t, certFile) @@ -107,13 +106,12 @@ func TestCertCommandFailures(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - app := CmdCert() + app := cmdCert() tempDir := t.TempDir() certFile := filepath.Join(tempDir, "cert.pem") keyFile := filepath.Join(tempDir, "key.pem") - args := append(c.args, "--out", certFile, "--keyout", keyFile) - err := app.Run(t.Context(), args) + err := app.Run(t.Context(), append(c.args, "--out", certFile, "--keyout", keyFile)) require.Error(t, err) if c.errMsg != "" { assert.ErrorContains(t, err, c.errMsg) diff --git a/cmd/main.go b/cmd/main.go index 318860397b001..0bca7c7466a04 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -149,7 +149,7 @@ func NewMainApp(appVer AppVersion) *cli.Command { // these sub-commands do not need the config file, and they do not depend on any path or environment variable. subCmdStandalone := []*cli.Command{ - CmdCert(), + cmdCert(), CmdGenerate, CmdDocs, } From b2d5244fed63fa1d6f50116d0d552ae80cfa3ade Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 4 Jun 2025 23:09:26 +0800 Subject: [PATCH 18/18] fix --- cmd/main.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 0bca7c7466a04..128b8776b4a5f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -29,8 +29,8 @@ func cmdHelp() *cli.Command { if c.Name == "help" { targetCmdIdx = 1 } - if lineage[targetCmdIdx].Name != "Gitea" { - err = cli.ShowCommandHelp(ctx, lineage[targetCmdIdx], lineage[targetCmdIdx].Name) + if lineage[targetCmdIdx] != lineage[targetCmdIdx].Root() { + err = cli.ShowCommandHelp(ctx, lineage[targetCmdIdx+1] /* parent cmd */, lineage[targetCmdIdx].Name /* sub cmd */) } else { err = cli.ShowAppHelp(c) } @@ -119,14 +119,12 @@ type AppVersion struct { } func NewMainApp(appVer AppVersion) *cli.Command { - app := &cli.Command{ - Name: "Gitea", - // HelpName: "gitea", - Usage: "A painless self-hosted Git service", - Description: `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.`, - Version: appVer.Version + appVer.Extra, - EnableShellCompletion: true, - } + app := &cli.Command{} + app.Name = "gitea" // must be lower-cased because it appears in the "USAGE" section like "gitea doctor [command [command options]]" + app.Usage = "A painless self-hosted Git service" + app.Description = `Gitea program contains "web" and other subcommands. If no subcommand is given, it starts the web server by default. Use "web" subcommand for more web server arguments, use other subcommands for other purposes.` + app.Version = appVer.Version + appVer.Extra + app.EnableShellCompletion = true // these sub-commands need to use config file subCmdWithConfig := []*cli.Command{