From af46a8e9936890ba6933fad73c5e60f14f5920aa Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Wed, 17 Apr 2024 15:31:23 -0500 Subject: [PATCH 1/5] First refactor --- .gitignore | 1 + .vscode/launch.json | 40 ++++++++++++ README.md | 10 +-- app/app.go | 58 +++++------------ app/types.go | 150 +++++++++++++++++++++++++++++++++++++++++++- cmd/disable.go | 39 +++++------- cmd/enable.go | 100 ++++++++++++++++------------- cmd/enable_test.go | 1 + cmd/list.go | 71 ++++++++------------- cmd/prune.go | 64 +++++++++++++++++++ cmd/remove.go | 41 ++++++------ cmd/root.go | 28 ++++++--- go.mod | 19 ++++++ go.sum | 63 ++++++++++++++++++- 14 files changed, 492 insertions(+), 193 deletions(-) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 cmd/enable_test.go create mode 100644 cmd/prune.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db85d14 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +copr-cli diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..96b2f15 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,40 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "List", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "main.go", + "args": ["list"] + }, + { + "name": "Prune", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "main.go", + "args": ["prune"] + }, + { + "name": "Enable", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "main.go", + "args": ["enable", "kylegospo/bazzite"] + }, + { + "name": "Launch file", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "main.go", + "args": ["disable", "kylegospo/bazzite"] + }, + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 1b1b47d..bd11dac 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ # copr-tool -CLI app for managing COPR repos, written in Go. +CLI app for managing Copr repos, written in Go. ```shell Usage: copr-tool [OPTION] [REPO...] Options: - enable Add or enable one or more COPR repositories. - remove Remove one or more COPR repositories. - list List all (enabled and disabled) COPR repositories in your repo folder. - disable Disable one or more COPR repositories without deleting the repository files. + enable Add or enable one or more Copr repositories. + remove Remove one or more Copr repositories. + list List all (enabled and disabled) Copr repositories in your repo folder. + disable Disable one or more Copr repositories without deleting the repository files. help Display help text. Arguments: diff --git a/app/app.go b/app/app.go index 781f3e3..a37424a 100644 --- a/app/app.go +++ b/app/app.go @@ -1,11 +1,12 @@ package app import ( + "errors" "fmt" "io" + "io/fs" "log" "net/http" - "net/url" "os" "strings" @@ -20,14 +21,6 @@ const ( Disabled RepoState = "enabled=0" ) -func NewCoprRepo(repoPath string) (CoprRepo, error) { - repo := CoprRepo{ - User: strings.Split(repoPath, "/")[0], - Project: strings.Split(repoPath, "/")[1], - } - return repo, nil -} - func FedoraReleaseVersion() string { osRelease, err := ini.Load("/etc/os-release") if err != nil { @@ -37,33 +30,16 @@ func FedoraReleaseVersion() string { return osRelease.Section("").Key("VERSION_ID").String() } -func RepoFileUrl(r CoprRepo) *url.URL { - fedoraRelease := "fedora-" + FedoraReleaseVersion() - repoName := r.User + "-" + r.Project + "-" + fedoraRelease + ".repo" - base, err := url.Parse(CoprUrl) - if err != nil { - log.Fatal(err) +func HandleError(err error) { + if errors.Is(err, fs.ErrPermission) { + fmt.Printf("This command must be run with superuser privileges.\nError: %s\n", err) + } else { + fmt.Println(err) } - repoUrl := base.JoinPath(r.User, r.Project, "repo", fedoraRelease, repoName) - return repoUrl -} - -func RepoFileName(r CoprRepo) string { - fileName := strings.Join([]string{"_copr", CoprHost, r.User, r.Project + ".repo"}, ":") - return fileName -} - -func RepoFilePath(r CoprRepo) string { - return ReposDir + RepoFileName(r) -} - -func RepoExists(r CoprRepo) bool { - _, err := os.Stat(RepoFilePath(r)) - return !os.IsNotExist(err) } -func GetLocalRepoFileLines(r CoprRepo) ([]string, error) { - repoFile := RepoFilePath(r) +func GetLocalRepoFileLines(r *CoprRepo) ([]string, error) { + repoFile := r.LocalFilePath() contents, err := os.ReadFile(repoFile) if err != nil { return nil, err @@ -72,15 +48,15 @@ func GetLocalRepoFileLines(r CoprRepo) ([]string, error) { return strings.Split(string(contents), "\n"), nil } -func WriteRepoToFile(r CoprRepo, content []byte) error { - err := os.WriteFile(RepoFilePath(r), content, 0644) +func WriteRepoToFile(r *CoprRepo, content []byte) error { + err := os.WriteFile(r.LocalFilePath(), content, 0644) if err != nil { return err } return nil } -func ToggleRepo(r CoprRepo, desiredState RepoState) error { +func ToggleRepo(r *CoprRepo, desiredState RepoState) error { fileLines, err := GetLocalRepoFileLines(r) if err != nil { return err @@ -111,8 +87,8 @@ func ToggleRepo(r CoprRepo, desiredState RepoState) error { return nil } -func AddRepo(r CoprRepo) error { - resp, err := http.Get(RepoFileUrl(r).String()) +func AddRepo(r *CoprRepo) error { + resp, err := http.Get(r.RepoConfigUrl()) if err != nil { return err } @@ -128,9 +104,9 @@ func AddRepo(r CoprRepo) error { return nil } -func DeleteRepo(r CoprRepo) error { - if RepoExists(r) { - err := os.Remove(RepoFilePath(r)) +func DeleteRepo(r *CoprRepo) error { + if r.LocalFileExists() { + err := os.Remove(r.LocalFilePath()) if err != nil { return err } diff --git a/app/types.go b/app/types.go index 1f3a27f..913e107 100644 --- a/app/types.go +++ b/app/types.go @@ -1,10 +1,156 @@ package app +import ( + "bufio" + "fmt" + "log" + "net/url" + "os" + "slices" + "strings" + + "github.com/spf13/afero" +) + type ( RepoState string ) type CoprRepo struct { - User string - Project string + User string + Project string + LocalFiles []string +} + +func NewCoprRepo(repoName string) (*CoprRepo, error) { + repo := &CoprRepo{ + User: strings.Split(repoName, "/")[0], + Project: strings.Split(repoName, "/")[1], + } + return repo, nil +} + +func (c *CoprRepo) Name() string { + return strings.Join([]string{c.User, c.Project}, "/") +} + +func (c *CoprRepo) RepoUrl() string { + base, err := url.Parse(CoprUrl) + if err != nil { + log.Fatal(err) + } + return base.JoinPath(c.Name()).String() +} + +func (c *CoprRepo) RemoteFileName() string { + return strings.Join([]string{c.User, c.Project, FedoraReleaseVersion()}, "-") + ".repo" +} + +func (c *CoprRepo) RepoConfigUrl() string { + fedoraRelease := "fedora-" + FedoraReleaseVersion() + base, err := url.Parse(c.RepoUrl()) + if err != nil { + log.Fatal(err) + } + repoUrl := base.JoinPath("repo", fedoraRelease, c.RemoteFileName()) + return repoUrl.String() +} + +func (c *CoprRepo) DefaultLocalFileName() string { + fileName := strings.Join([]string{"_copr", CoprHost, c.User, c.Project + ".repo"}, ":") + return fileName +} + +func (c *CoprRepo) LocalFilePath() string { + return ReposDir + c.DefaultLocalFileName() +} + +func (c *CoprRepo) LocalFileExists() bool { + _, err := os.Stat(c.LocalFilePath()) + return !os.IsNotExist(err) +} + +func (c *CoprRepo) FindLocalFiles(fs afero.Fs) error { + files, err := afero.ReadDir(fs, ReposDir) + if err != nil { + return err + } + for _, file := range files { + result, err := afero.FileContainsBytes(fs, ReposDir+file.Name(), []byte(c.Name())) + if err != nil { + return err + } + if result { + c.LocalFiles = append(c.LocalFiles, file.Name()) + } + } + return nil +} + +func GetAllRepos() ([]*CoprRepo, error) { + files, err := os.ReadDir(ReposDir) + if err != nil { + return nil, err + } + var reposStrings []string + var repos []*CoprRepo + for _, file := range files { + if !file.IsDir() { + ioFile, err := os.Open(ReposDir + file.Name()) + + if err != nil { + return nil, err + } + + scanner := bufio.NewScanner(ioFile) + for scanner.Scan() { + if strings.Contains(scanner.Text(), "[copr:copr") { + t := strings.Split(strings.Trim(scanner.Text(), "[]"), ":") + // r, _ := app.NewCoprRepo(t[len(t)-2] + "/" + t[len(t)-1]) + repoName := t[len(t)-2] + "/" + t[len(t)-1] + if !slices.Contains(reposStrings, repoName) { + r, err := NewCoprRepo(repoName) + if err != nil { + return nil, err + } + repos = append(repos, r) + reposStrings = append(reposStrings, repoName) + } + break + } + } + if err := scanner.Err(); err != nil { + fmt.Fprintln(os.Stderr, "Issue reading repo files: ", err) + } + } + } + return repos, nil +} + +func (c *CoprRepo) PruneDuplicates(fs afero.Fs) (bool, error) { + if len(c.LocalFiles) == 0 { + fmt.Printf("Repository %s is not installed.", c.Name()) + } else if len(c.LocalFiles) > 1 { + if _, err := fs.Open(ReposDir + c.DefaultLocalFileName()); err != nil { + err := fs.Rename(ReposDir+c.LocalFiles[0], ReposDir+c.DefaultLocalFileName()) + if err != nil { + return false, err + } + c.LocalFiles[0] = c.DefaultLocalFileName() + } + pruneCount := 0 + for _, fileName := range c.LocalFiles { + if fileName != c.DefaultLocalFileName() { + err := fs.Remove(ReposDir + fileName) + if err != nil { + return true, err + } + pruneCount++ + //TODO remove the element from LocalFiles + } + } + fmt.Printf("Pruned %d duplicate entries for %s\n", pruneCount, c.Name()) + return true, nil + } + return false, nil } diff --git a/cmd/disable.go b/cmd/disable.go index 9c49ace..42edd80 100644 --- a/cmd/disable.go +++ b/cmd/disable.go @@ -4,39 +4,34 @@ Copyright © 2024 NAME HERE package cmd import ( - "errors" "fmt" - "io/fs" "os" "github.com/spf13/cobra" "github.com/trgeiger/copr-tool/app" ) -// disableCmd represents the disable command -var disableCmd = &cobra.Command{ - Use: "disable", - Args: cobra.MinimumNArgs(1), - Short: "Disable one or more COPR repositories without removing their configuration files.", - Run: func(cmd *cobra.Command, args []string) { - for _, arg := range args { - repo, err := app.NewCoprRepo(arg) - if err != nil { - fmt.Println(err) - } - err = app.ToggleRepo(repo, app.Disabled) - if err != nil { - if errors.Is(err, fs.ErrPermission) { - fmt.Printf("This command must be run with superuser privileges.\nError: %s\n", err) - } else { +func NewDisableCmd() *cobra.Command { + return &cobra.Command{ + Use: "disable", + Args: cobra.MinimumNArgs(1), + Short: "Disable one or more Copr repositories without uninstalling them.", + Run: func(cmd *cobra.Command, args []string) { + for _, arg := range args { + repo, err := app.NewCoprRepo(arg) + if err != nil { fmt.Println(err) } - os.Exit(1) + err = app.ToggleRepo(repo, app.Disabled) + if err != nil { + app.HandleError(err) + os.Exit(1) + } } - } - }, + }, + } } func init() { - rootCmd.AddCommand(disableCmd) + rootCmd.AddCommand(NewDisableCmd()) } diff --git a/cmd/enable.go b/cmd/enable.go index 8b46787..d040707 100644 --- a/cmd/enable.go +++ b/cmd/enable.go @@ -4,62 +4,50 @@ Copyright © 2024 NAME HERE package cmd import ( - "errors" "fmt" - "io/fs" - "log" "net/http" "os" + "github.com/spf13/afero" "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/trgeiger/copr-tool/app" ) -// addCmd represents the add command -var enableCmd = &cobra.Command{ - Use: "enable", - Aliases: []string{"add"}, - Args: cobra.MinimumNArgs(1), - Short: "Enable or add one or more COPR repositories.", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - Run: func(cmd *cobra.Command, args []string) { - for _, arg := range args { - repo, err := app.NewCoprRepo(arg) - if err != nil { - fmt.Println(err) - } - err = enableRepo(repo) - if err != nil { - if errors.Is(err, fs.ErrPermission) { - fmt.Printf("This command must be run with superuser privileges.\nError: %s\n", err) - } else { - fmt.Println(err) - } - os.Exit(1) - } - } - }, -} - -func verifyCoprRepo(r app.CoprRepo) error { - _, err := http.Get(app.RepoFileUrl(r).String()) +func verifyCoprRepo(r *app.CoprRepo) error { + resp, err := http.Get(r.RepoUrl()) if err != nil { return err } - + if resp.StatusCode == 404 { + return fmt.Errorf("repository does not exist, %s returned 404", r.RepoUrl()) + } + resp, err = http.Get(r.RepoConfigUrl()) + if err != nil { + return err + } + if resp.StatusCode == 404 { + return fmt.Errorf("repository %s does not support Fedora release %s", r.Name(), app.FedoraReleaseVersion()) + } return nil } -func enableRepo(r app.CoprRepo) error { - if verifyCoprRepo(r) != nil { - log.Fatal("Repository verification failed. Double-check repository name.") +func enableRepo(r *app.CoprRepo) error { + if err := verifyCoprRepo(r); err != nil { + return err + } + fs := afero.NewOsFs() + err := r.FindLocalFiles(fs) + if err != nil { + return err } - if app.RepoExists(r) { + // if len(r.LocalFiles) > 1 { + // err := r.PruneDuplicates(fs) + // if err != nil { + // return err + // } + // } + if r.LocalFileExists() { err := app.ToggleRepo(r, app.Enabled) if err != nil { return err @@ -74,7 +62,35 @@ func enableRepo(r app.CoprRepo) error { return nil } +func NewEnableCmd(config *viper.Viper) *cobra.Command { + return &cobra.Command{ + Use: "enable", + Aliases: []string{"add"}, + Args: cobra.MinimumNArgs(1), + Short: "Enable or add one or more Copr repositories.", + Long: `A longer description that spans multiple lines and likely contains examples + and usage of using your command. For example: + + Cobra is a CLI library for Go that empowers applications. + This application is a tool to generate the needed files + to quickly create a Cobra application.`, + Run: func(cmd *cobra.Command, args []string) { + for _, arg := range args { + repo, err := app.NewCoprRepo(arg) + if err != nil { + fmt.Println(err) + } + err = enableRepo(repo) + if err != nil { + app.HandleError(err) + os.Exit(1) + } + } + }, + } +} + func init() { - rootCmd.AddCommand(enableCmd) + rootCmd.AddCommand(NewEnableCmd(viper.GetViper())) } diff --git a/cmd/enable_test.go b/cmd/enable_test.go new file mode 100644 index 0000000..1d619dd --- /dev/null +++ b/cmd/enable_test.go @@ -0,0 +1 @@ +package cmd diff --git a/cmd/list.go b/cmd/list.go index c111e5d..93d01ba 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -4,65 +4,44 @@ Copyright © 2024 NAME HERE package cmd import ( - "bufio" "fmt" - "os" - "strings" + "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/trgeiger/copr-tool/app" ) -// listCmd represents the list command -var listCmd = &cobra.Command{ - Use: "list", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - Run: func(cmd *cobra.Command, args []string) { - listRepos() - }, -} - -func listRepos() error { - files, err := os.ReadDir(app.ReposDir) - if err != nil { - return err - } - - for _, file := range files { - if !file.IsDir() { - ioFile, err := os.Open(app.ReposDir + file.Name()) - +func NewListCmd() *cobra.Command { + return &cobra.Command{ + Use: "list", + Short: "List installed Copr repositories", + Long: `A longer description that spans multiple lines and likely contains examples + and usage of using your command. For example: + + Cobra is a CLI library for Go that empowers applications. + This application is a tool to generate the needed files + to quickly create a Cobra application.`, + Run: func(cmd *cobra.Command, args []string) { + repos, err := app.GetAllRepos() if err != nil { - return err + fmt.Printf("Error when retrieving locally installed repositories: %s", err) } - - scanner := bufio.NewScanner(ioFile) - for scanner.Scan() { - if strings.Contains(scanner.Text(), "[copr:copr") { - t := strings.Split(strings.Trim(scanner.Text(), "[]"), ":") - r, _ := app.NewCoprRepo(t[len(t)-2] + "/" + t[len(t)-1]) - properFileName := app.RepoFileName(r) - if file.Name() != properFileName { - fmt.Printf("Repository %s detected with non-standard repository file name.", r.User+"/"+r.Project) - } - fmt.Println(strings.Join(t[len(t)-2:], "/")) - break + showDupesMessage := false + for _, r := range repos { + fs := afero.NewOsFs() + r.FindLocalFiles(fs) + if len(r.LocalFiles) > 1 { + showDupesMessage = true } + fmt.Println(r.Name()) } - if err := scanner.Err(); err != nil { - fmt.Fprintln(os.Stderr, "Issue reading repo files: ", err) + if showDupesMessage { + fmt.Println("\nDuplicate entries found. Consider running the prune command.") } - } + }, } - return nil } func init() { - rootCmd.AddCommand(listCmd) + rootCmd.AddCommand(NewListCmd()) } diff --git a/cmd/prune.go b/cmd/prune.go new file mode 100644 index 0000000..c204353 --- /dev/null +++ b/cmd/prune.go @@ -0,0 +1,64 @@ +/* +Copyright © 2024 NAME HERE +*/ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/afero" + "github.com/spf13/cobra" + "github.com/trgeiger/copr-tool/app" +) + +// pruneCmd represents the prune command +// var pruneCmd = &cobra.Command{ +// Use: "prune", +// Short: "A brief description of your command", +// Long: `A longer description that spans multiple lines and likely contains examples +// and usage of using your command. For example: + +// Cobra is a CLI library for Go that empowers applications. +// This application is a tool to generate the needed files +// to quickly create a Cobra application.`, +// Run: func(cmd *cobra.Command, args []string) { +// fmt.Println("prune called") +// }, +// } + +func NewPruneCmd() *cobra.Command { + return &cobra.Command{ + Use: "prune", + Short: "Remove duplicate repository configurations.", + Run: func(cmd *cobra.Command, args []string) { + fs := afero.NewOsFs() + repos, err := app.GetAllRepos() + if err != nil { + fmt.Printf("Error when retrieving locally installed repositories: %s", err) + os.Exit(1) + } + pruneCount := 0 + for _, r := range repos { + r.FindLocalFiles(fs) + pruned, err := r.PruneDuplicates(fs) + if pruned && err == nil { + pruneCount++ + } else if pruned && err != nil { + fmt.Printf("Pruning attempted on %s but encountered error: %s", r.Name(), err) + os.Exit(1) + } else if err != nil { + fmt.Printf("Error encountered: %s", err) + os.Exit(1) + } + } + if pruneCount == 0 { + fmt.Println("Nothing to prune.") + } + }, + } +} + +func init() { + rootCmd.AddCommand(NewPruneCmd()) +} diff --git a/cmd/remove.go b/cmd/remove.go index 8b8b081..23076a8 100644 --- a/cmd/remove.go +++ b/cmd/remove.go @@ -4,40 +4,35 @@ Copyright © 2024 NAME HERE package cmd import ( - "errors" "fmt" - "io/fs" "os" "github.com/spf13/cobra" "github.com/trgeiger/copr-tool/app" ) -// removeCmd represents the remove command -var removeCmd = &cobra.Command{ - Use: "remove", - Aliases: []string{"delete"}, - Args: cobra.MinimumNArgs(1), - Short: "Remove one or more COPR repositories' configuration files.", - Run: func(cmd *cobra.Command, args []string) { - for _, arg := range args { - repo, err := app.NewCoprRepo(arg) - if err != nil { - fmt.Println(err) - } - err = app.DeleteRepo(repo) - if err != nil { - if errors.Is(err, fs.ErrPermission) { - fmt.Printf("This command must be run with superuser privileges.\nError: %s\n", err) - } else { +func NewRemoveCmd() *cobra.Command { + return &cobra.Command{ + Use: "remove", + Aliases: []string{"delete"}, + Args: cobra.MinimumNArgs(1), + Short: "Uninstall one or more Copr repositories.", + Run: func(cmd *cobra.Command, args []string) { + for _, arg := range args { + repo, err := app.NewCoprRepo(arg) + if err != nil { fmt.Println(err) } - os.Exit(1) + err = app.DeleteRepo(repo) + if err != nil { + app.HandleError(err) + os.Exit(1) + } } - } - }, + }, + } } func init() { - rootCmd.AddCommand(removeCmd) + rootCmd.AddCommand(NewRemoveCmd()) } diff --git a/cmd/root.go b/cmd/root.go index 7156d13..795c13a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,21 +8,14 @@ import ( "os" "github.com/spf13/cobra" + "github.com/spf13/viper" ) // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "copr-tool", - Short: "A brief description of your application", - Long: `A longer description that spans multiple lines and likely contains -examples and usage of using your application. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, + Short: "A command line tool for managing Copr repositories.", + Long: `TODO`, } // Execute adds all child commands to the root command and sets flags appropriately. @@ -36,6 +29,21 @@ func Execute() { } func init() { + viper.SetConfigName("os-release") + viper.SetConfigType("ini") + viper.AddConfigPath("/etc/") + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + panic(fmt.Errorf("could not fine /etc/os-release, copr-tool only functions on Fedora Linux systems: %w", err)) + + } else { + panic(fmt.Errorf("unknown fatal error: %w", err)) + } + } + if viper.Get("default.id") != "fedora" { + fmt.Println("Non-Fedora distribution detected. Copr tool only functions on Fedora Linux.") + os.Exit(1) + } // Here you will define your flags and configuration settings. // Cobra supports persistent flags, which, if defined here, // will be global for your application. diff --git a/go.mod b/go.mod index 7513af0..f1a1fa8 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,30 @@ go 1.22.2 require ( github.com/go-ini/ini v1.67.0 + github.com/spf13/afero v1.11.0 github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.18.2 ) require ( + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.9.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 290490d..71d7135 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,78 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 347956d992900623dc05aef48a3ac69a711a600b Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Thu, 18 Apr 2024 15:24:41 -0500 Subject: [PATCH 2/5] feat: Fix output and add tests for enable command --- .vscode/launch.json | 16 ++++- app/app.go | 39 ++++++----- app/types.go | 17 +++-- cmd/disable.go | 14 ++-- cmd/enable.go | 30 +++----- cmd/enable_test.go | 69 +++++++++++++++++++ cmd/list.go | 16 ++--- cmd/prune.go | 22 +++--- cmd/remove.go | 14 ++-- cmd/root.go | 37 +++++++--- ...acloud.org:kylegospo:bazzite.repo-disabled | 10 +++ ...racloud.org:kylegospo:bazzite.repo-enabled | 10 +++ main.go | 5 +- 13 files changed, 200 insertions(+), 99 deletions(-) create mode 100644 cmd/test/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo-disabled create mode 100644 cmd/test/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo-enabled diff --git a/.vscode/launch.json b/.vscode/launch.json index 96b2f15..6731fc3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,16 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "Test enable", + "type": "go", + "request": "launch", + "mode": "test", + "program": "${workspaceFolder}/cmd", + "args": [ + "" + ] + }, { "name": "List", "type": "go", @@ -20,7 +30,7 @@ "program": "main.go", "args": ["prune"] }, - { + { "name": "Enable", "type": "go", "request": "launch", @@ -28,8 +38,8 @@ "program": "main.go", "args": ["enable", "kylegospo/bazzite"] }, - { - "name": "Launch file", + { + "name": "Disable", "type": "go", "request": "launch", "mode": "debug", diff --git a/app/app.go b/app/app.go index a37424a..0a4ceef 100644 --- a/app/app.go +++ b/app/app.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/go-ini/ini" + "github.com/spf13/afero" ) const ( @@ -30,17 +31,17 @@ func FedoraReleaseVersion() string { return osRelease.Section("").Key("VERSION_ID").String() } -func HandleError(err error) { +func HandleError(err error, out io.Writer) { if errors.Is(err, fs.ErrPermission) { - fmt.Printf("This command must be run with superuser privileges.\nError: %s\n", err) + fmt.Fprintf(out, "This command must be run with superuser privileges.\nError: %s\n", err) } else { - fmt.Println(err) + fmt.Fprintln(out, err) } } -func GetLocalRepoFileLines(r *CoprRepo) ([]string, error) { +func GetLocalRepoFileLines(r *CoprRepo, fs afero.Fs) ([]string, error) { repoFile := r.LocalFilePath() - contents, err := os.ReadFile(repoFile) + contents, err := afero.ReadFile(fs, repoFile) if err != nil { return nil, err } @@ -48,16 +49,16 @@ func GetLocalRepoFileLines(r *CoprRepo) ([]string, error) { return strings.Split(string(contents), "\n"), nil } -func WriteRepoToFile(r *CoprRepo, content []byte) error { - err := os.WriteFile(r.LocalFilePath(), content, 0644) +func WriteRepoToFile(r *CoprRepo, fs afero.Fs, content []byte) error { + err := afero.WriteFile(fs, r.LocalFilePath(), content, 0644) if err != nil { return err } return nil } -func ToggleRepo(r *CoprRepo, desiredState RepoState) error { - fileLines, err := GetLocalRepoFileLines(r) +func ToggleRepo(r *CoprRepo, fs afero.Fs, out io.Writer, desiredState RepoState) error { + fileLines, err := GetLocalRepoFileLines(r, fs) if err != nil { return err } @@ -71,7 +72,7 @@ func ToggleRepo(r *CoprRepo, desiredState RepoState) error { for i, line := range fileLines { if strings.Contains(line, "enabled=") { if line == string(desiredState) { - fmt.Printf("Repository is already %s.\n", statusMessage) + fmt.Fprintf(out, "Repository %s is already %s.\n", r.Name(), statusMessage) return nil } else { fileLines[i] = string(desiredState) @@ -79,15 +80,15 @@ func ToggleRepo(r *CoprRepo, desiredState RepoState) error { } } output := strings.Join(fileLines, "\n") - err = WriteRepoToFile(r, []byte(output)) + err = WriteRepoToFile(r, fs, []byte(output)) if err != nil { return err } - fmt.Printf("Repository %s/%s %s.\n", r.User, r.Project, statusMessage) + fmt.Fprintf(out, "Repository %s/%s %s.\n", r.User, r.Project, statusMessage) return nil } -func AddRepo(r *CoprRepo) error { +func AddRepo(r *CoprRepo, fs afero.Fs, out io.Writer) error { resp, err := http.Get(r.RepoConfigUrl()) if err != nil { return err @@ -96,23 +97,23 @@ func AddRepo(r *CoprRepo) error { if err != nil { return err } - err = WriteRepoToFile(r, []byte(output)) + err = WriteRepoToFile(r, fs, []byte(output)) if err != nil { return err } - fmt.Printf("Repository %s/%s added.\n", r.User, r.Project) + fmt.Fprintf(out, "Repository %s/%s added.\n", r.User, r.Project) return nil } -func DeleteRepo(r *CoprRepo) error { - if r.LocalFileExists() { +func DeleteRepo(r *CoprRepo, fs afero.Fs, out io.Writer) error { + if r.LocalFileExists(fs) { err := os.Remove(r.LocalFilePath()) if err != nil { return err } - fmt.Printf("Repository %s/%s deleted.\n", r.User, r.Project) + fmt.Fprintf(out, "Repository %s/%s deleted.\n", r.User, r.Project) } else { - fmt.Printf("Repository %s/%s does not exist locally. Nothing to delete.\n", r.User, r.Project) + fmt.Fprintf(out, "Repository %s/%s does not exist locally. Nothing to delete.\n", r.User, r.Project) } return nil } diff --git a/app/types.go b/app/types.go index 913e107..6470592 100644 --- a/app/types.go +++ b/app/types.go @@ -3,9 +3,11 @@ package app import ( "bufio" "fmt" + "io" "log" "net/url" "os" + "regexp" "slices" "strings" @@ -23,6 +25,9 @@ type CoprRepo struct { } func NewCoprRepo(repoName string) (*CoprRepo, error) { + if matched, _ := regexp.MatchString(`\w*\/\w*`, repoName); !matched { + return nil, fmt.Errorf("invalid repository name: %s", repoName) + } repo := &CoprRepo{ User: strings.Split(repoName, "/")[0], Project: strings.Split(repoName, "/")[1], @@ -65,8 +70,8 @@ func (c *CoprRepo) LocalFilePath() string { return ReposDir + c.DefaultLocalFileName() } -func (c *CoprRepo) LocalFileExists() bool { - _, err := os.Stat(c.LocalFilePath()) +func (c *CoprRepo) LocalFileExists(fs afero.Fs) bool { + _, err := fs.Stat(c.LocalFilePath()) return !os.IsNotExist(err) } @@ -87,7 +92,7 @@ func (c *CoprRepo) FindLocalFiles(fs afero.Fs) error { return nil } -func GetAllRepos() ([]*CoprRepo, error) { +func GetAllRepos(fs afero.Fs) ([]*CoprRepo, error) { files, err := os.ReadDir(ReposDir) if err != nil { return nil, err @@ -127,9 +132,9 @@ func GetAllRepos() ([]*CoprRepo, error) { return repos, nil } -func (c *CoprRepo) PruneDuplicates(fs afero.Fs) (bool, error) { +func (c *CoprRepo) PruneDuplicates(fs afero.Fs, out io.Writer) (bool, error) { if len(c.LocalFiles) == 0 { - fmt.Printf("Repository %s is not installed.", c.Name()) + fmt.Fprintf(out, "Repository %s is not installed.", c.Name()) } else if len(c.LocalFiles) > 1 { if _, err := fs.Open(ReposDir + c.DefaultLocalFileName()); err != nil { err := fs.Rename(ReposDir+c.LocalFiles[0], ReposDir+c.DefaultLocalFileName()) @@ -149,7 +154,7 @@ func (c *CoprRepo) PruneDuplicates(fs afero.Fs) (bool, error) { //TODO remove the element from LocalFiles } } - fmt.Printf("Pruned %d duplicate entries for %s\n", pruneCount, c.Name()) + fmt.Fprintf(out, "Pruned %d duplicate entries for %s\n", pruneCount, c.Name()) return true, nil } return false, nil diff --git a/cmd/disable.go b/cmd/disable.go index 42edd80..be66cf2 100644 --- a/cmd/disable.go +++ b/cmd/disable.go @@ -5,13 +5,15 @@ package cmd import ( "fmt" + "io" "os" + "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/trgeiger/copr-tool/app" ) -func NewDisableCmd() *cobra.Command { +func NewDisableCmd(fs afero.Fs, out io.Writer) *cobra.Command { return &cobra.Command{ Use: "disable", Args: cobra.MinimumNArgs(1), @@ -20,18 +22,14 @@ func NewDisableCmd() *cobra.Command { for _, arg := range args { repo, err := app.NewCoprRepo(arg) if err != nil { - fmt.Println(err) + fmt.Fprintln(out, err) } - err = app.ToggleRepo(repo, app.Disabled) + err = app.ToggleRepo(repo, fs, out, app.Disabled) if err != nil { - app.HandleError(err) + app.HandleError(err, out) os.Exit(1) } } }, } } - -func init() { - rootCmd.AddCommand(NewDisableCmd()) -} diff --git a/cmd/enable.go b/cmd/enable.go index d040707..2cf0f8c 100644 --- a/cmd/enable.go +++ b/cmd/enable.go @@ -5,12 +5,12 @@ package cmd import ( "fmt" + "io" "net/http" "os" "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/spf13/viper" "github.com/trgeiger/copr-tool/app" ) @@ -32,29 +32,22 @@ func verifyCoprRepo(r *app.CoprRepo) error { return nil } -func enableRepo(r *app.CoprRepo) error { +func enableRepo(r *app.CoprRepo, fs afero.Fs, out io.Writer) error { if err := verifyCoprRepo(r); err != nil { return err } - fs := afero.NewOsFs() err := r.FindLocalFiles(fs) if err != nil { return err } - // if len(r.LocalFiles) > 1 { - // err := r.PruneDuplicates(fs) - // if err != nil { - // return err - // } - // } - if r.LocalFileExists() { - err := app.ToggleRepo(r, app.Enabled) + if r.LocalFileExists(fs) { + err := app.ToggleRepo(r, fs, out, app.Enabled) if err != nil { return err } return nil } else { - err := app.AddRepo(r) + err := app.AddRepo(r, fs, out) if err != nil { return err } @@ -62,7 +55,7 @@ func enableRepo(r *app.CoprRepo) error { return nil } -func NewEnableCmd(config *viper.Viper) *cobra.Command { +func NewEnableCmd(fs afero.Fs, out io.Writer) *cobra.Command { return &cobra.Command{ Use: "enable", Aliases: []string{"add"}, @@ -78,19 +71,14 @@ func NewEnableCmd(config *viper.Viper) *cobra.Command { for _, arg := range args { repo, err := app.NewCoprRepo(arg) if err != nil { - fmt.Println(err) + fmt.Fprintln(out, err) } - err = enableRepo(repo) + err = enableRepo(repo, fs, out) if err != nil { - app.HandleError(err) + app.HandleError(err, out) os.Exit(1) } } }, } } - -func init() { - rootCmd.AddCommand(NewEnableCmd(viper.GetViper())) - -} diff --git a/cmd/enable_test.go b/cmd/enable_test.go index 1d619dd..5b82043 100644 --- a/cmd/enable_test.go +++ b/cmd/enable_test.go @@ -1 +1,70 @@ package cmd + +import ( + "bytes" + "fmt" + "testing" + + "github.com/spf13/afero" + "github.com/trgeiger/copr-tool/app" +) + +func TestEnableCmd(t *testing.T) { + tests := []struct { + name string + args []string + localFiles [][]string + expected string + }{ + { + name: "Add valid repo", + args: []string{ + "kylegospo/bazzite", + }, + expected: "Repository kylegospo/bazzite added.\n", + }, + { + name: "Repo already exists and already enabled", + args: []string{ + "kylegospo/bazzite", + }, + localFiles: [][]string{ + {"_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo", "enabled"}, + }, + expected: "Repository kylegospo/bazzite is already enabled.\n", + }, + { + name: "Repo already exists but not enabled", + args: []string{ + "kylegospo/bazzite", + }, + localFiles: [][]string{ + {"_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo", "disabled"}, + }, + expected: "Repository kylegospo/bazzite enabled.\n", + }, + } + + for _, test := range tests { + fs := afero.NewMemMapFs() + fs.Mkdir("/etc/yum.repos.d/", 0755) + b := new(bytes.Buffer) + cmd := NewEnableCmd(fs, b) + cmd.SetOut(b) + cmd.SetArgs(test.args) + if test.localFiles != nil { + for _, file := range test.localFiles { + localFs := afero.NewOsFs() + testFile, _ := afero.ReadFile(localFs, "./test/"+file[0]+"-"+file[1]) + _ = afero.WriteFile(fs, app.ReposDir+file[0], testFile, 0755) + } + } + cmd.Execute() + poop := b.String() + fmt.Print(poop) + if b.String() != test.expected { + t.Fatalf("Test: \"%s\" failed", test.name) + } + } + +} diff --git a/cmd/list.go b/cmd/list.go index 93d01ba..1dae4d8 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -5,13 +5,14 @@ package cmd import ( "fmt" + "io" "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/trgeiger/copr-tool/app" ) -func NewListCmd() *cobra.Command { +func NewListCmd(fs afero.Fs, out io.Writer) *cobra.Command { return &cobra.Command{ Use: "list", Short: "List installed Copr repositories", @@ -22,26 +23,21 @@ func NewListCmd() *cobra.Command { This application is a tool to generate the needed files to quickly create a Cobra application.`, Run: func(cmd *cobra.Command, args []string) { - repos, err := app.GetAllRepos() + repos, err := app.GetAllRepos(fs) if err != nil { - fmt.Printf("Error when retrieving locally installed repositories: %s", err) + fmt.Fprintf(out, "Error when retrieving locally installed repositories: %s", err) } showDupesMessage := false for _, r := range repos { - fs := afero.NewOsFs() r.FindLocalFiles(fs) if len(r.LocalFiles) > 1 { showDupesMessage = true } - fmt.Println(r.Name()) + fmt.Fprintln(out, r.Name()) } if showDupesMessage { - fmt.Println("\nDuplicate entries found. Consider running the prune command.") + fmt.Fprintln(out, "\nDuplicate entries found. Consider running the prune command.") } }, } } - -func init() { - rootCmd.AddCommand(NewListCmd()) -} diff --git a/cmd/prune.go b/cmd/prune.go index c204353..255d66d 100644 --- a/cmd/prune.go +++ b/cmd/prune.go @@ -5,6 +5,7 @@ package cmd import ( "fmt" + "io" "os" "github.com/spf13/afero" @@ -23,42 +24,37 @@ import ( // This application is a tool to generate the needed files // to quickly create a Cobra application.`, // Run: func(cmd *cobra.Command, args []string) { -// fmt.Println("prune called") +// fmt.Fprintln(out, "prune called") // }, // } -func NewPruneCmd() *cobra.Command { +func NewPruneCmd(fs afero.Fs, out io.Writer) *cobra.Command { return &cobra.Command{ Use: "prune", Short: "Remove duplicate repository configurations.", Run: func(cmd *cobra.Command, args []string) { - fs := afero.NewOsFs() - repos, err := app.GetAllRepos() + repos, err := app.GetAllRepos(fs) if err != nil { - fmt.Printf("Error when retrieving locally installed repositories: %s", err) + fmt.Fprintf(out, "Error when retrieving locally installed repositories: %s", err) os.Exit(1) } pruneCount := 0 for _, r := range repos { r.FindLocalFiles(fs) - pruned, err := r.PruneDuplicates(fs) + pruned, err := r.PruneDuplicates(fs, out) if pruned && err == nil { pruneCount++ } else if pruned && err != nil { - fmt.Printf("Pruning attempted on %s but encountered error: %s", r.Name(), err) + fmt.Fprintf(out, "Pruning attempted on %s but encountered error: %s", r.Name(), err) os.Exit(1) } else if err != nil { - fmt.Printf("Error encountered: %s", err) + fmt.Fprintf(out, "Error encountered: %s", err) os.Exit(1) } } if pruneCount == 0 { - fmt.Println("Nothing to prune.") + fmt.Fprintln(out, "Nothing to prune.") } }, } } - -func init() { - rootCmd.AddCommand(NewPruneCmd()) -} diff --git a/cmd/remove.go b/cmd/remove.go index 23076a8..7cbf4f2 100644 --- a/cmd/remove.go +++ b/cmd/remove.go @@ -5,13 +5,15 @@ package cmd import ( "fmt" + "io" "os" + "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/trgeiger/copr-tool/app" ) -func NewRemoveCmd() *cobra.Command { +func NewRemoveCmd(fs afero.Fs, out io.Writer) *cobra.Command { return &cobra.Command{ Use: "remove", Aliases: []string{"delete"}, @@ -21,18 +23,14 @@ func NewRemoveCmd() *cobra.Command { for _, arg := range args { repo, err := app.NewCoprRepo(arg) if err != nil { - fmt.Println(err) + fmt.Fprintln(out, err) } - err = app.DeleteRepo(repo) + err = app.DeleteRepo(repo, fs, out) if err != nil { - app.HandleError(err) + app.HandleError(err, out) os.Exit(1) } } }, } } - -func init() { - rootCmd.AddCommand(NewRemoveCmd()) -} diff --git a/cmd/root.go b/cmd/root.go index 795c13a..b43ba6a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,29 +5,49 @@ package cmd import ( "fmt" + "io" "os" + "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/spf13/viper" ) -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "copr-tool", - Short: "A command line tool for managing Copr repositories.", - Long: `TODO`, -} - // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { - err := rootCmd.Execute() + fs := afero.NewOsFs() + cmd, err := NewRootCmd(fs, os.Stdout) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + err = cmd.Execute() if err != nil { fmt.Println(err) os.Exit(1) } } +func NewRootCmd(fs afero.Fs, out io.Writer) (*cobra.Command, error) { + + cmd := &cobra.Command{ + Use: "copr-tool", + Short: "A command line tool for managing Copr repositories", + } + + cmd.AddCommand( + NewDisableCmd(fs, out), + NewEnableCmd(fs, out), + NewListCmd(fs, out), + NewPruneCmd(fs, out), + NewRemoveCmd(fs, out), + ) + + return cmd, nil +} + func init() { viper.SetConfigName("os-release") viper.SetConfigType("ini") @@ -52,5 +72,4 @@ func init() { // Cobra also supports local flags, which will only run // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } diff --git a/cmd/test/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo-disabled b/cmd/test/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo-disabled new file mode 100644 index 0000000..c83c771 --- /dev/null +++ b/cmd/test/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo-disabled @@ -0,0 +1,10 @@ +[copr:copr.fedorainfracloud.org:kylegospo:bazzite] +name=Copr repo for bazzite owned by kylegospo +baseurl=https://download.copr.fedorainfracloud.org/results/kylegospo/bazzite/fedora-$releasever-$basearch/ +type=rpm-md +skip_if_unavailable=True +gpgcheck=1 +gpgkey=https://download.copr.fedorainfracloud.org/results/kylegospo/bazzite/pubkey.gpg +repo_gpgcheck=0 +enabled=0 +enabled_metadata=1 diff --git a/cmd/test/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo-enabled b/cmd/test/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo-enabled new file mode 100644 index 0000000..6db2e69 --- /dev/null +++ b/cmd/test/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo-enabled @@ -0,0 +1,10 @@ +[copr:copr.fedorainfracloud.org:kylegospo:bazzite] +name=Copr repo for bazzite owned by kylegospo +baseurl=https://download.copr.fedorainfracloud.org/results/kylegospo/bazzite/fedora-$releasever-$basearch/ +type=rpm-md +skip_if_unavailable=True +gpgcheck=1 +gpgkey=https://download.copr.fedorainfracloud.org/results/kylegospo/bazzite/pubkey.gpg +repo_gpgcheck=0 +enabled=1 +enabled_metadata=1 diff --git a/main.go b/main.go index 05f600f..1fe8751 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,11 @@ /* Copyright © 2024 NAME HERE - */ package main -import "github.com/trgeiger/copr-tool/cmd" +import ( + "github.com/trgeiger/copr-tool/cmd" +) func main() { cmd.Execute() From 5885fd3f2a24d396422ebbf99803c90f401803fb Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Fri, 19 Apr 2024 15:43:14 -0500 Subject: [PATCH 3/5] Improve Enable tests, fix Fedora Release function Also reorganize everything into internal dir --- .vscode/settings.json | 6 ++ cmd/disable.go | 4 +- cmd/enable.go | 31 ++++--- cmd/enable_test.go | 54 +++++++++---- cmd/list.go | 2 +- cmd/prune.go | 2 +- cmd/remove.go | 4 +- cmd/root.go | 35 ++++---- ...dorainfracloud.org:kylegospo:bazzite.repo} | 0 ...dorainfracloud.org:kylegospo:bazzite.repo} | 0 cmd/test/f30/os-release | 4 + cmd/test/f40/os-release | 4 + go.mod | 1 - go.sum | 2 - app/types.go => internal/app/copr-repo.go | 52 ++---------- app/app.go => internal/app/util.go | 80 ++++++++++++++----- internal/testutil/test-utils.go | 21 +++++ main.go | 4 +- 18 files changed, 180 insertions(+), 126 deletions(-) create mode 100644 .vscode/settings.json rename cmd/test/{_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo-disabled => disabled/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo} (100%) rename cmd/test/{_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo-enabled => enabled/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo} (100%) create mode 100644 cmd/test/f30/os-release create mode 100644 cmd/test/f40/os-release rename app/types.go => internal/app/copr-repo.go (68%) rename app/app.go => internal/app/util.go (60%) create mode 100644 internal/testutil/test-utils.go diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3304163 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "go.testFlags": [ + "-v", + "-coverpkg=all" + ] +} \ No newline at end of file diff --git a/cmd/disable.go b/cmd/disable.go index be66cf2..95e5fb8 100644 --- a/cmd/disable.go +++ b/cmd/disable.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/trgeiger/copr-tool/app" + "github.com/trgeiger/copr-tool/internal/app" ) func NewDisableCmd(fs afero.Fs, out io.Writer) *cobra.Command { @@ -26,7 +26,7 @@ func NewDisableCmd(fs afero.Fs, out io.Writer) *cobra.Command { } err = app.ToggleRepo(repo, fs, out, app.Disabled) if err != nil { - app.HandleError(err, out) + app.SudoMessage(err, out) os.Exit(1) } } diff --git a/cmd/enable.go b/cmd/enable.go index 2cf0f8c..07d5e62 100644 --- a/cmd/enable.go +++ b/cmd/enable.go @@ -7,14 +7,13 @@ import ( "fmt" "io" "net/http" - "os" "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/trgeiger/copr-tool/app" + "github.com/trgeiger/copr-tool/internal/app" ) -func verifyCoprRepo(r *app.CoprRepo) error { +func verifyCoprRepo(r *app.CoprRepo, fs afero.Fs) error { resp, err := http.Get(r.RepoUrl()) if err != nil { return err @@ -22,18 +21,18 @@ func verifyCoprRepo(r *app.CoprRepo) error { if resp.StatusCode == 404 { return fmt.Errorf("repository does not exist, %s returned 404", r.RepoUrl()) } - resp, err = http.Get(r.RepoConfigUrl()) + resp, err = http.Get(r.RepoConfigUrl(fs)) if err != nil { return err } if resp.StatusCode == 404 { - return fmt.Errorf("repository %s does not support Fedora release %s", r.Name(), app.FedoraReleaseVersion()) + return fmt.Errorf("repository %s does not support Fedora release %s", r.Name(), app.FedoraReleaseVersion(fs)) } return nil } func enableRepo(r *app.CoprRepo, fs afero.Fs, out io.Writer) error { - if err := verifyCoprRepo(r); err != nil { + if err := verifyCoprRepo(r, fs); err != nil { return err } err := r.FindLocalFiles(fs) @@ -62,21 +61,21 @@ func NewEnableCmd(fs afero.Fs, out io.Writer) *cobra.Command { Args: cobra.MinimumNArgs(1), Short: "Enable or add one or more Copr repositories.", Long: `A longer description that spans multiple lines and likely contains examples - and usage of using your command. For example: - - Cobra is a CLI library for Go that empowers applications. - This application is a tool to generate the needed files - to quickly create a Cobra application.`, + and usage of using your command. For example: + + Cobra is a CLI library for Go that empowers applications. + This application is a tool to generate the needed files + to quickly create a Cobra application.`, Run: func(cmd *cobra.Command, args []string) { for _, arg := range args { repo, err := app.NewCoprRepo(arg) if err != nil { fmt.Fprintln(out, err) - } - err = enableRepo(repo, fs, out) - if err != nil { - app.HandleError(err, out) - os.Exit(1) + } else { + err = enableRepo(repo, fs, out) + if err != nil { + app.SudoMessage(err, out) + } } } }, diff --git a/cmd/enable_test.go b/cmd/enable_test.go index 5b82043..b256727 100644 --- a/cmd/enable_test.go +++ b/cmd/enable_test.go @@ -5,15 +5,15 @@ import ( "fmt" "testing" - "github.com/spf13/afero" - "github.com/trgeiger/copr-tool/app" + "github.com/trgeiger/copr-tool/internal/testutil" ) func TestEnableCmd(t *testing.T) { tests := []struct { name string args []string - localFiles [][]string + repoFiles [][]string // format: file/reponame, test directory folder + otherFiles [][]string // format: filename, path, test directory folder expected string }{ { @@ -22,13 +22,40 @@ func TestEnableCmd(t *testing.T) { "kylegospo/bazzite", }, expected: "Repository kylegospo/bazzite added.\n", + otherFiles: [][]string{ + {"os-release", "/etc/", "f40"}, + }, + }, + { + name: "Add invalid repo name", + args: []string{ + "copr-tool", + }, + expected: "invalid repository name: copr-tool\n", + }, + { + name: "Repo does not exist", + args: []string{ + "example/example", + }, + expected: "repository does not exist, https://copr.fedorainfracloud.org/coprs/example/example returned 404\n", + }, + { + name: "Repo does not support Fedora version", + args: []string{ + "kylegospo/bazzite", + }, + otherFiles: [][]string{ + {"os-release", "/etc/", "f30"}, + }, + expected: "repository kylegospo/bazzite does not support Fedora release 30\n", }, { name: "Repo already exists and already enabled", args: []string{ "kylegospo/bazzite", }, - localFiles: [][]string{ + repoFiles: [][]string{ {"_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo", "enabled"}, }, expected: "Repository kylegospo/bazzite is already enabled.\n", @@ -38,7 +65,7 @@ func TestEnableCmd(t *testing.T) { args: []string{ "kylegospo/bazzite", }, - localFiles: [][]string{ + repoFiles: [][]string{ {"_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo", "disabled"}, }, expected: "Repository kylegospo/bazzite enabled.\n", @@ -46,22 +73,17 @@ func TestEnableCmd(t *testing.T) { } for _, test := range tests { - fs := afero.NewMemMapFs() - fs.Mkdir("/etc/yum.repos.d/", 0755) + b := new(bytes.Buffer) + fs := testutil.AssembleTestFs(test.repoFiles, test.otherFiles) cmd := NewEnableCmd(fs, b) cmd.SetOut(b) cmd.SetArgs(test.args) - if test.localFiles != nil { - for _, file := range test.localFiles { - localFs := afero.NewOsFs() - testFile, _ := afero.ReadFile(localFs, "./test/"+file[0]+"-"+file[1]) - _ = afero.WriteFile(fs, app.ReposDir+file[0], testFile, 0755) - } - } + cmd.Execute() - poop := b.String() - fmt.Print(poop) + + outB := b.String() + fmt.Print(outB) if b.String() != test.expected { t.Fatalf("Test: \"%s\" failed", test.name) } diff --git a/cmd/list.go b/cmd/list.go index 1dae4d8..572b9ef 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/trgeiger/copr-tool/app" + "github.com/trgeiger/copr-tool/internal/app" ) func NewListCmd(fs afero.Fs, out io.Writer) *cobra.Command { diff --git a/cmd/prune.go b/cmd/prune.go index 255d66d..a24e0fd 100644 --- a/cmd/prune.go +++ b/cmd/prune.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/trgeiger/copr-tool/app" + "github.com/trgeiger/copr-tool/internal/app" ) // pruneCmd represents the prune command diff --git a/cmd/remove.go b/cmd/remove.go index 7cbf4f2..e81dde0 100644 --- a/cmd/remove.go +++ b/cmd/remove.go @@ -10,7 +10,7 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/trgeiger/copr-tool/app" + "github.com/trgeiger/copr-tool/internal/app" ) func NewRemoveCmd(fs afero.Fs, out io.Writer) *cobra.Command { @@ -27,7 +27,7 @@ func NewRemoveCmd(fs afero.Fs, out io.Writer) *cobra.Command { } err = app.DeleteRepo(repo, fs, out) if err != nil { - app.HandleError(err, out) + app.SudoMessage(err, out) os.Exit(1) } } diff --git a/cmd/root.go b/cmd/root.go index b43ba6a..fe30ea5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -15,8 +15,25 @@ import ( // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - fs := afero.NewOsFs() +func Execute(fs afero.Fs) { + + viper.SetConfigName("os-release") + viper.SetConfigType("ini") + viper.AddConfigPath("/etc/") + viper.SetFs(fs) + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + panic(fmt.Errorf("could not find /etc/os-release, copr-tool only functions on Fedora Linux systems: %w", err)) + + } else { + panic(fmt.Errorf("unknown fatal error: %w", err)) + } + } + if viper.Get("default.id") != "fedora" { + fmt.Println("Non-Fedora distribution detected. Copr tool only functions on Fedora Linux.") + os.Exit(1) + } + cmd, err := NewRootCmd(fs, os.Stdout) if err != nil { fmt.Println(err) @@ -49,21 +66,7 @@ func NewRootCmd(fs afero.Fs, out io.Writer) (*cobra.Command, error) { } func init() { - viper.SetConfigName("os-release") - viper.SetConfigType("ini") - viper.AddConfigPath("/etc/") - if err := viper.ReadInConfig(); err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); ok { - panic(fmt.Errorf("could not fine /etc/os-release, copr-tool only functions on Fedora Linux systems: %w", err)) - } else { - panic(fmt.Errorf("unknown fatal error: %w", err)) - } - } - if viper.Get("default.id") != "fedora" { - fmt.Println("Non-Fedora distribution detected. Copr tool only functions on Fedora Linux.") - os.Exit(1) - } // Here you will define your flags and configuration settings. // Cobra supports persistent flags, which, if defined here, // will be global for your application. diff --git a/cmd/test/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo-disabled b/cmd/test/disabled/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo similarity index 100% rename from cmd/test/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo-disabled rename to cmd/test/disabled/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo diff --git a/cmd/test/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo-enabled b/cmd/test/enabled/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo similarity index 100% rename from cmd/test/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo-enabled rename to cmd/test/enabled/_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo diff --git a/cmd/test/f30/os-release b/cmd/test/f30/os-release new file mode 100644 index 0000000..3859919 --- /dev/null +++ b/cmd/test/f30/os-release @@ -0,0 +1,4 @@ +NAME="Fedora Linux" +ID=fedora +VERSION_ID=30 +PLATFORM_ID="platform:f30" diff --git a/cmd/test/f40/os-release b/cmd/test/f40/os-release new file mode 100644 index 0000000..c0a9c58 --- /dev/null +++ b/cmd/test/f40/os-release @@ -0,0 +1,4 @@ +NAME="Fedora Linux" +ID=fedora +VERSION_ID=40 +PLATFORM_ID="platform:f40" diff --git a/go.mod b/go.mod index f1a1fa8..7b96645 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/trgeiger/copr-tool go 1.22.2 require ( - github.com/go-ini/ini v1.67.0 github.com/spf13/afero v1.11.0 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 diff --git a/go.sum b/go.sum index 71d7135..88f6d97 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,6 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= -github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= diff --git a/app/types.go b/internal/app/copr-repo.go similarity index 68% rename from app/types.go rename to internal/app/copr-repo.go index 6470592..e21a38c 100644 --- a/app/types.go +++ b/internal/app/copr-repo.go @@ -1,14 +1,12 @@ package app import ( - "bufio" "fmt" "io" "log" "net/url" "os" "regexp" - "slices" "strings" "github.com/spf13/afero" @@ -47,17 +45,17 @@ func (c *CoprRepo) RepoUrl() string { return base.JoinPath(c.Name()).String() } -func (c *CoprRepo) RemoteFileName() string { - return strings.Join([]string{c.User, c.Project, FedoraReleaseVersion()}, "-") + ".repo" +func (c *CoprRepo) RemoteFileName(fs afero.Fs) string { + return strings.Join([]string{c.User, c.Project, FedoraReleaseVersion(fs)}, "-") + ".repo" } -func (c *CoprRepo) RepoConfigUrl() string { - fedoraRelease := "fedora-" + FedoraReleaseVersion() +func (c *CoprRepo) RepoConfigUrl(fs afero.Fs) string { + fedoraRelease := "fedora-" + FedoraReleaseVersion(fs) base, err := url.Parse(c.RepoUrl()) if err != nil { log.Fatal(err) } - repoUrl := base.JoinPath("repo", fedoraRelease, c.RemoteFileName()) + repoUrl := base.JoinPath("repo", fedoraRelease, c.RemoteFileName(fs)) return repoUrl.String() } @@ -92,46 +90,6 @@ func (c *CoprRepo) FindLocalFiles(fs afero.Fs) error { return nil } -func GetAllRepos(fs afero.Fs) ([]*CoprRepo, error) { - files, err := os.ReadDir(ReposDir) - if err != nil { - return nil, err - } - var reposStrings []string - var repos []*CoprRepo - for _, file := range files { - if !file.IsDir() { - ioFile, err := os.Open(ReposDir + file.Name()) - - if err != nil { - return nil, err - } - - scanner := bufio.NewScanner(ioFile) - for scanner.Scan() { - if strings.Contains(scanner.Text(), "[copr:copr") { - t := strings.Split(strings.Trim(scanner.Text(), "[]"), ":") - // r, _ := app.NewCoprRepo(t[len(t)-2] + "/" + t[len(t)-1]) - repoName := t[len(t)-2] + "/" + t[len(t)-1] - if !slices.Contains(reposStrings, repoName) { - r, err := NewCoprRepo(repoName) - if err != nil { - return nil, err - } - repos = append(repos, r) - reposStrings = append(reposStrings, repoName) - } - break - } - } - if err := scanner.Err(); err != nil { - fmt.Fprintln(os.Stderr, "Issue reading repo files: ", err) - } - } - } - return repos, nil -} - func (c *CoprRepo) PruneDuplicates(fs afero.Fs, out io.Writer) (bool, error) { if len(c.LocalFiles) == 0 { fmt.Fprintf(out, "Repository %s is not installed.", c.Name()) diff --git a/app/app.go b/internal/app/util.go similarity index 60% rename from app/app.go rename to internal/app/util.go index 0a4ceef..3d26f04 100644 --- a/app/app.go +++ b/internal/app/util.go @@ -1,17 +1,18 @@ package app import ( + "bufio" "errors" "fmt" "io" "io/fs" - "log" "net/http" "os" + "slices" "strings" - "github.com/go-ini/ini" "github.com/spf13/afero" + "github.com/spf13/viper" ) const ( @@ -22,16 +23,20 @@ const ( Disabled RepoState = "enabled=0" ) -func FedoraReleaseVersion() string { - osRelease, err := ini.Load("/etc/os-release") - if err != nil { - log.Fatal("Fail to read file: ", err) - } +func FedoraReleaseVersion(fs afero.Fs) string { + // osRelease, err := ini.Load("/etc/os-release") + reader := viper.New() + reader.SetFs(fs) + reader.SetConfigName("os-release") + reader.SetConfigType("ini") + reader.AddConfigPath("/etc/") + reader.ReadInConfig() + osRelease := reader.GetString("default.version_id") - return osRelease.Section("").Key("VERSION_ID").String() + return osRelease } -func HandleError(err error, out io.Writer) { +func SudoMessage(err error, out io.Writer) { if errors.Is(err, fs.ErrPermission) { fmt.Fprintf(out, "This command must be run with superuser privileges.\nError: %s\n", err) } else { @@ -39,16 +44,6 @@ func HandleError(err error, out io.Writer) { } } -func GetLocalRepoFileLines(r *CoprRepo, fs afero.Fs) ([]string, error) { - repoFile := r.LocalFilePath() - contents, err := afero.ReadFile(fs, repoFile) - if err != nil { - return nil, err - } - - return strings.Split(string(contents), "\n"), nil -} - func WriteRepoToFile(r *CoprRepo, fs afero.Fs, content []byte) error { err := afero.WriteFile(fs, r.LocalFilePath(), content, 0644) if err != nil { @@ -58,10 +53,13 @@ func WriteRepoToFile(r *CoprRepo, fs afero.Fs, content []byte) error { } func ToggleRepo(r *CoprRepo, fs afero.Fs, out io.Writer, desiredState RepoState) error { - fileLines, err := GetLocalRepoFileLines(r, fs) + repoFile := r.LocalFilePath() + contents, err := afero.ReadFile(fs, repoFile) if err != nil { return err } + fileLines := strings.Split(string(contents), "\n") + var statusMessage string if desiredState == Enabled { statusMessage = "enabled" @@ -89,7 +87,7 @@ func ToggleRepo(r *CoprRepo, fs afero.Fs, out io.Writer, desiredState RepoState) } func AddRepo(r *CoprRepo, fs afero.Fs, out io.Writer) error { - resp, err := http.Get(r.RepoConfigUrl()) + resp, err := http.Get(r.RepoConfigUrl(fs)) if err != nil { return err } @@ -117,3 +115,43 @@ func DeleteRepo(r *CoprRepo, fs afero.Fs, out io.Writer) error { } return nil } + +func GetAllRepos(fs afero.Fs) ([]*CoprRepo, error) { + files, err := os.ReadDir(ReposDir) + if err != nil { + return nil, err + } + var reposStrings []string + var repos []*CoprRepo + for _, file := range files { + if !file.IsDir() { + ioFile, err := os.Open(ReposDir + file.Name()) + + if err != nil { + return nil, err + } + + scanner := bufio.NewScanner(ioFile) + for scanner.Scan() { + if strings.Contains(scanner.Text(), "[copr:copr") { + t := strings.Split(strings.Trim(scanner.Text(), "[]"), ":") + // r, _ := app.NewCoprRepo(t[len(t)-2] + "/" + t[len(t)-1]) + repoName := t[len(t)-2] + "/" + t[len(t)-1] + if !slices.Contains(reposStrings, repoName) { + r, err := NewCoprRepo(repoName) + if err != nil { + return nil, err + } + repos = append(repos, r) + reposStrings = append(reposStrings, repoName) + } + break + } + } + if err := scanner.Err(); err != nil { + fmt.Fprintln(os.Stderr, "Issue reading repo files: ", err) + } + } + } + return repos, nil +} diff --git a/internal/testutil/test-utils.go b/internal/testutil/test-utils.go new file mode 100644 index 0000000..08c075e --- /dev/null +++ b/internal/testutil/test-utils.go @@ -0,0 +1,21 @@ +package testutil + +import ( + "github.com/spf13/afero" + "github.com/trgeiger/copr-tool/internal/app" +) + +func AssembleTestFs(repoFiles [][]string, otherFiles [][]string) afero.Fs { + fs := afero.NewMemMapFs() + fs.Mkdir("/etc/yum.repos.d/", 0755) + localFs := afero.NewOsFs() + for _, file := range repoFiles { + testFile, _ := afero.ReadFile(localFs, "./test/"+file[1]+"/"+file[0]) + _ = afero.WriteFile(fs, app.ReposDir+file[0], testFile, 0755) + } + for _, file := range otherFiles { + testFile, _ := afero.ReadFile(localFs, "./test/"+file[2]+"/"+file[0]) + _ = afero.WriteFile(fs, file[1]+file[0], testFile, 0755) + } + return fs +} diff --git a/main.go b/main.go index 1fe8751..66d37d2 100644 --- a/main.go +++ b/main.go @@ -4,9 +4,11 @@ Copyright © 2024 NAME HERE package main import ( + "github.com/spf13/afero" "github.com/trgeiger/copr-tool/cmd" ) func main() { - cmd.Execute() + fs := afero.NewOsFs() + cmd.Execute(fs) } From 82034aac47bb245c53aeecc3ad5fb2a5a44248f7 Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Fri, 19 Apr 2024 16:05:39 -0500 Subject: [PATCH 4/5] Fix a few lingering uses of os for files --- .gitignore | 1 + cmd/enable.go | 2 ++ cmd/list.go | 2 +- cmd/prune.go | 2 +- internal/app/util.go | 11 +++++------ 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index db85d14..047f46e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ copr-cli +copr-tool \ No newline at end of file diff --git a/cmd/enable.go b/cmd/enable.go index 07d5e62..70c25f0 100644 --- a/cmd/enable.go +++ b/cmd/enable.go @@ -42,6 +42,8 @@ func enableRepo(r *app.CoprRepo, fs afero.Fs, out io.Writer) error { if r.LocalFileExists(fs) { err := app.ToggleRepo(r, fs, out, app.Enabled) if err != nil { + app.SudoMessage(err, out) + return err } return nil diff --git a/cmd/list.go b/cmd/list.go index 572b9ef..6d8d5a0 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -23,7 +23,7 @@ func NewListCmd(fs afero.Fs, out io.Writer) *cobra.Command { This application is a tool to generate the needed files to quickly create a Cobra application.`, Run: func(cmd *cobra.Command, args []string) { - repos, err := app.GetAllRepos(fs) + repos, err := app.GetAllRepos(fs, out) if err != nil { fmt.Fprintf(out, "Error when retrieving locally installed repositories: %s", err) } diff --git a/cmd/prune.go b/cmd/prune.go index a24e0fd..e6675e5 100644 --- a/cmd/prune.go +++ b/cmd/prune.go @@ -33,7 +33,7 @@ func NewPruneCmd(fs afero.Fs, out io.Writer) *cobra.Command { Use: "prune", Short: "Remove duplicate repository configurations.", Run: func(cmd *cobra.Command, args []string) { - repos, err := app.GetAllRepos(fs) + repos, err := app.GetAllRepos(fs, out) if err != nil { fmt.Fprintf(out, "Error when retrieving locally installed repositories: %s", err) os.Exit(1) diff --git a/internal/app/util.go b/internal/app/util.go index 3d26f04..be9e36a 100644 --- a/internal/app/util.go +++ b/internal/app/util.go @@ -7,7 +7,6 @@ import ( "io" "io/fs" "net/http" - "os" "slices" "strings" @@ -105,7 +104,7 @@ func AddRepo(r *CoprRepo, fs afero.Fs, out io.Writer) error { func DeleteRepo(r *CoprRepo, fs afero.Fs, out io.Writer) error { if r.LocalFileExists(fs) { - err := os.Remove(r.LocalFilePath()) + err := fs.Remove(r.LocalFilePath()) if err != nil { return err } @@ -116,8 +115,8 @@ func DeleteRepo(r *CoprRepo, fs afero.Fs, out io.Writer) error { return nil } -func GetAllRepos(fs afero.Fs) ([]*CoprRepo, error) { - files, err := os.ReadDir(ReposDir) +func GetAllRepos(fs afero.Fs, out io.Writer) ([]*CoprRepo, error) { + files, err := afero.ReadDir(fs, ReposDir) if err != nil { return nil, err } @@ -125,7 +124,7 @@ func GetAllRepos(fs afero.Fs) ([]*CoprRepo, error) { var repos []*CoprRepo for _, file := range files { if !file.IsDir() { - ioFile, err := os.Open(ReposDir + file.Name()) + ioFile, err := fs.Open(ReposDir + file.Name()) if err != nil { return nil, err @@ -149,7 +148,7 @@ func GetAllRepos(fs afero.Fs) ([]*CoprRepo, error) { } } if err := scanner.Err(); err != nil { - fmt.Fprintln(os.Stderr, "Issue reading repo files: ", err) + fmt.Fprintln(out, "Issue reading repo files: ", err) } } } From 115de1a813b34f145b0c8f42f02e64a1fb8e5681 Mon Sep 17 00:00:00 2001 From: Tayler Geiger Date: Fri, 19 Apr 2024 21:28:08 -0500 Subject: [PATCH 5/5] Add more tests Added list, prune, disable, and remove tests. --- .vscode/launch.json | 2 +- cmd/disable.go | 11 ++- cmd/disable_test.go | 68 +++++++++++++++++++ cmd/enable_test.go | 3 - cmd/list.go | 22 +++--- cmd/list_test.go | 45 ++++++++++++ cmd/prune.go | 2 - cmd/prune_test.go | 53 +++++++++++++++ cmd/remove.go | 11 ++- cmd/remove_test.go | 58 ++++++++++++++++ ...cloud.org:bieszczaders:kernel-cachyos.repo | 10 +++ ...infracloud.org:kylegospo:bazzite-copy.repo | 10 +++ ...nfracloud.org:kylegospo:bazzite-copy2.repo | 10 +++ internal/app/copr-repo.go | 6 +- internal/app/util.go | 4 +- 15 files changed, 286 insertions(+), 29 deletions(-) create mode 100644 cmd/disable_test.go create mode 100644 cmd/list_test.go create mode 100644 cmd/prune_test.go create mode 100644 cmd/remove_test.go create mode 100644 cmd/test/enabled/_copr:copr.fedorainfracloud.org:bieszczaders:kernel-cachyos.repo create mode 100644 cmd/test/enabled/_copr:copr.fedorainfracloud.org:kylegospo:bazzite-copy.repo create mode 100644 cmd/test/enabled/_copr:copr.fedorainfracloud.org:kylegospo:bazzite-copy2.repo diff --git a/.vscode/launch.json b/.vscode/launch.json index 6731fc3..76bb340 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "version": "0.2.0", "configurations": [ { - "name": "Test enable", + "name": "Test", "type": "go", "request": "launch", "mode": "test", diff --git a/cmd/disable.go b/cmd/disable.go index 95e5fb8..a43da5f 100644 --- a/cmd/disable.go +++ b/cmd/disable.go @@ -6,7 +6,6 @@ package cmd import ( "fmt" "io" - "os" "github.com/spf13/afero" "github.com/spf13/cobra" @@ -23,11 +22,11 @@ func NewDisableCmd(fs afero.Fs, out io.Writer) *cobra.Command { repo, err := app.NewCoprRepo(arg) if err != nil { fmt.Fprintln(out, err) - } - err = app.ToggleRepo(repo, fs, out, app.Disabled) - if err != nil { - app.SudoMessage(err, out) - os.Exit(1) + } else { + err = app.ToggleRepo(repo, fs, out, app.Disabled) + if err != nil { + app.SudoMessage(err, out) + } } } }, diff --git a/cmd/disable_test.go b/cmd/disable_test.go new file mode 100644 index 0000000..03612e2 --- /dev/null +++ b/cmd/disable_test.go @@ -0,0 +1,68 @@ +package cmd + +import ( + "bytes" + "testing" + + "github.com/trgeiger/copr-tool/internal/testutil" +) + +func TestDisableCmd(t *testing.T) { + tests := []struct { + name string + args []string + repoFiles [][]string // format: file/reponame, test directory folder + otherFiles [][]string // format: filename, path, test directory folder + expected string + }{ + { + name: "Disable invalid repo name", + args: []string{ + "copr-tool", + }, + expected: "invalid repository name: copr-tool\n", + }, + { + name: "Repo does not exist", + args: []string{ + "example/example", + }, + expected: "repository example/example is not installed\n", + }, + { + name: "Repo already exists and already disabled", + args: []string{ + "kylegospo/bazzite", + }, + repoFiles: [][]string{ + {"_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo", "disabled"}, + }, + expected: "Repository kylegospo/bazzite is already disabled.\n", + }, + { + name: "Repo already exists but not disabled", + args: []string{ + "kylegospo/bazzite", + }, + repoFiles: [][]string{ + {"_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo", "enabled"}, + }, + expected: "Repository kylegospo/bazzite disabled.\n", + }, + } + + for _, test := range tests { + + b := new(bytes.Buffer) + fs := testutil.AssembleTestFs(test.repoFiles, test.otherFiles) + cmd := NewDisableCmd(fs, b) + cmd.SetOut(b) + cmd.SetArgs(test.args) + + cmd.Execute() + + if b.String() != test.expected { + t.Fatalf("Test \"%s\" failed", test.name) + } + } +} diff --git a/cmd/enable_test.go b/cmd/enable_test.go index b256727..8b0d4eb 100644 --- a/cmd/enable_test.go +++ b/cmd/enable_test.go @@ -2,7 +2,6 @@ package cmd import ( "bytes" - "fmt" "testing" "github.com/trgeiger/copr-tool/internal/testutil" @@ -82,8 +81,6 @@ func TestEnableCmd(t *testing.T) { cmd.Execute() - outB := b.String() - fmt.Print(outB) if b.String() != test.expected { t.Fatalf("Test: \"%s\" failed", test.name) } diff --git a/cmd/list.go b/cmd/list.go index 6d8d5a0..1dd0fbd 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -27,16 +27,20 @@ func NewListCmd(fs afero.Fs, out io.Writer) *cobra.Command { if err != nil { fmt.Fprintf(out, "Error when retrieving locally installed repositories: %s", err) } - showDupesMessage := false - for _, r := range repos { - r.FindLocalFiles(fs) - if len(r.LocalFiles) > 1 { - showDupesMessage = true + if len(repos) == 0 { + fmt.Fprintln(out, "No installed Copr repositories.") + } else { + showDupesMessage := false + for _, r := range repos { + r.FindLocalFiles(fs) + if len(r.LocalFiles) > 1 { + showDupesMessage = true + } + fmt.Fprintln(out, r.Name()) + } + if showDupesMessage { + fmt.Fprintln(out, "\nDuplicate entries found. Consider running the prune command.") } - fmt.Fprintln(out, r.Name()) - } - if showDupesMessage { - fmt.Fprintln(out, "\nDuplicate entries found. Consider running the prune command.") } }, } diff --git a/cmd/list_test.go b/cmd/list_test.go new file mode 100644 index 0000000..26cccd8 --- /dev/null +++ b/cmd/list_test.go @@ -0,0 +1,45 @@ +package cmd + +import ( + "bytes" + "testing" + + "github.com/trgeiger/copr-tool/internal/testutil" +) + +func TestListCmd(t *testing.T) { + tests := []struct { + name string + repoFiles [][]string // format: file/reponame, test directory folder + otherFiles [][]string // format: filename, path, test directory folder + expected string + }{ + { + name: "List existing repos", + repoFiles: [][]string{ + {"_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo", "enabled"}, + {"_copr:copr.fedorainfracloud.org:bieszczaders:kernel-cachyos.repo", "enabled"}, + }, + expected: "bieszczaders/kernel-cachyos\nkylegospo/bazzite\n", + }, + { + name: "No repos to list", + expected: "No installed Copr repositories.\n", + }, + } + + for _, test := range tests { + + b := new(bytes.Buffer) + fs := testutil.AssembleTestFs(test.repoFiles, test.otherFiles) + cmd := NewListCmd(fs, b) + cmd.SetOut(b) + + cmd.Execute() + + if b.String() != test.expected { + t.Fatalf("Test \"%s\" failed", test.name) + } + } + +} diff --git a/cmd/prune.go b/cmd/prune.go index e6675e5..c1d93a5 100644 --- a/cmd/prune.go +++ b/cmd/prune.go @@ -46,10 +46,8 @@ func NewPruneCmd(fs afero.Fs, out io.Writer) *cobra.Command { pruneCount++ } else if pruned && err != nil { fmt.Fprintf(out, "Pruning attempted on %s but encountered error: %s", r.Name(), err) - os.Exit(1) } else if err != nil { fmt.Fprintf(out, "Error encountered: %s", err) - os.Exit(1) } } if pruneCount == 0 { diff --git a/cmd/prune_test.go b/cmd/prune_test.go new file mode 100644 index 0000000..8aeabbf --- /dev/null +++ b/cmd/prune_test.go @@ -0,0 +1,53 @@ +package cmd + +import ( + "bytes" + "testing" + + "github.com/trgeiger/copr-tool/internal/testutil" +) + +func TestPruneCmd(t *testing.T) { + tests := []struct { + name string + repoFiles [][]string // format: file/reponame, test directory folder + otherFiles [][]string // format: filename, path, test directory folder + expected string + }{ + { + name: "No repositories installed", + expected: "Nothing to prune.\n", + }, + { + name: "Remove 1 duplicate", + repoFiles: [][]string{ + {"_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo", "enabled"}, + {"_copr:copr.fedorainfracloud.org:kylegospo:bazzite-copy.repo", "enabled"}, + }, + expected: "Removed 1 duplicate entry for kylegospo/bazzite.\n", + }, + { + name: "Remove multiple duplicates", + repoFiles: [][]string{ + {"_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo", "enabled"}, + {"_copr:copr.fedorainfracloud.org:kylegospo:bazzite-copy.repo", "enabled"}, + {"_copr:copr.fedorainfracloud.org:kylegospo:bazzite-copy2.repo", "enabled"}, + }, + expected: "Removed 2 duplicate entries for kylegospo/bazzite.\n", + }, + } + + for _, test := range tests { + + b := new(bytes.Buffer) + fs := testutil.AssembleTestFs(test.repoFiles, test.otherFiles) + cmd := NewPruneCmd(fs, b) + cmd.SetOut(b) + + cmd.Execute() + + if b.String() != test.expected { + t.Fatalf("Test \"%s\" failed", test.name) + } + } +} diff --git a/cmd/remove.go b/cmd/remove.go index e81dde0..9d75e08 100644 --- a/cmd/remove.go +++ b/cmd/remove.go @@ -6,7 +6,6 @@ package cmd import ( "fmt" "io" - "os" "github.com/spf13/afero" "github.com/spf13/cobra" @@ -24,11 +23,11 @@ func NewRemoveCmd(fs afero.Fs, out io.Writer) *cobra.Command { repo, err := app.NewCoprRepo(arg) if err != nil { fmt.Fprintln(out, err) - } - err = app.DeleteRepo(repo, fs, out) - if err != nil { - app.SudoMessage(err, out) - os.Exit(1) + } else { + err = app.DeleteRepo(repo, fs, out) + if err != nil { + app.SudoMessage(err, out) + } } } }, diff --git a/cmd/remove_test.go b/cmd/remove_test.go new file mode 100644 index 0000000..a4048bd --- /dev/null +++ b/cmd/remove_test.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "bytes" + "testing" + + "github.com/trgeiger/copr-tool/internal/testutil" +) + +func TestRemoveCmd(t *testing.T) { + tests := []struct { + name string + args []string + repoFiles [][]string // format: file/reponame, test directory folder + otherFiles [][]string // format: filename, path, test directory folder + expected string + }{ + { + name: "Remove invalid repo name", + args: []string{ + "copr-tool", + }, + expected: "invalid repository name: copr-tool\n", + }, + { + name: "Remove uninstalled repo", + args: []string{ + "example/example", + }, + expected: "Repository example/example does not exist locally. Nothing to delete.\n", + }, + { + name: "Remove installed repo", + args: []string{ + "kylegospo/bazzite", + }, + repoFiles: [][]string{ + {"_copr:copr.fedorainfracloud.org:kylegospo:bazzite.repo", "enabled"}, + }, + expected: "Repository kylegospo/bazzite deleted.\n", + }, + } + + for _, test := range tests { + + b := new(bytes.Buffer) + fs := testutil.AssembleTestFs(test.repoFiles, test.otherFiles) + cmd := NewRemoveCmd(fs, b) + cmd.SetOut(b) + cmd.SetArgs(test.args) + + cmd.Execute() + + if b.String() != test.expected { + t.Fatalf("Test \"%s\" failed", test.name) + } + } +} diff --git a/cmd/test/enabled/_copr:copr.fedorainfracloud.org:bieszczaders:kernel-cachyos.repo b/cmd/test/enabled/_copr:copr.fedorainfracloud.org:bieszczaders:kernel-cachyos.repo new file mode 100644 index 0000000..d273a03 --- /dev/null +++ b/cmd/test/enabled/_copr:copr.fedorainfracloud.org:bieszczaders:kernel-cachyos.repo @@ -0,0 +1,10 @@ +[copr:copr.fedorainfracloud.org:bieszczaders:kernel-cachyos] +name=Copr repo for kernel-cachyos owned by bieszczaders +baseurl=https://download.copr.fedorainfracloud.org/results/bieszczaders/kernel-cachyos/fedora-$releasever-$basearch/ +type=rpm-md +skip_if_unavailable=True +gpgcheck=1 +gpgkey=https://download.copr.fedorainfracloud.org/results/bieszczaders/kernel-cachyos/pubkey.gpg +repo_gpgcheck=0 +enabled=1 +enabled_metadata=1 diff --git a/cmd/test/enabled/_copr:copr.fedorainfracloud.org:kylegospo:bazzite-copy.repo b/cmd/test/enabled/_copr:copr.fedorainfracloud.org:kylegospo:bazzite-copy.repo new file mode 100644 index 0000000..6db2e69 --- /dev/null +++ b/cmd/test/enabled/_copr:copr.fedorainfracloud.org:kylegospo:bazzite-copy.repo @@ -0,0 +1,10 @@ +[copr:copr.fedorainfracloud.org:kylegospo:bazzite] +name=Copr repo for bazzite owned by kylegospo +baseurl=https://download.copr.fedorainfracloud.org/results/kylegospo/bazzite/fedora-$releasever-$basearch/ +type=rpm-md +skip_if_unavailable=True +gpgcheck=1 +gpgkey=https://download.copr.fedorainfracloud.org/results/kylegospo/bazzite/pubkey.gpg +repo_gpgcheck=0 +enabled=1 +enabled_metadata=1 diff --git a/cmd/test/enabled/_copr:copr.fedorainfracloud.org:kylegospo:bazzite-copy2.repo b/cmd/test/enabled/_copr:copr.fedorainfracloud.org:kylegospo:bazzite-copy2.repo new file mode 100644 index 0000000..6db2e69 --- /dev/null +++ b/cmd/test/enabled/_copr:copr.fedorainfracloud.org:kylegospo:bazzite-copy2.repo @@ -0,0 +1,10 @@ +[copr:copr.fedorainfracloud.org:kylegospo:bazzite] +name=Copr repo for bazzite owned by kylegospo +baseurl=https://download.copr.fedorainfracloud.org/results/kylegospo/bazzite/fedora-$releasever-$basearch/ +type=rpm-md +skip_if_unavailable=True +gpgcheck=1 +gpgkey=https://download.copr.fedorainfracloud.org/results/kylegospo/bazzite/pubkey.gpg +repo_gpgcheck=0 +enabled=1 +enabled_metadata=1 diff --git a/internal/app/copr-repo.go b/internal/app/copr-repo.go index e21a38c..b677a8f 100644 --- a/internal/app/copr-repo.go +++ b/internal/app/copr-repo.go @@ -112,7 +112,11 @@ func (c *CoprRepo) PruneDuplicates(fs afero.Fs, out io.Writer) (bool, error) { //TODO remove the element from LocalFiles } } - fmt.Fprintf(out, "Pruned %d duplicate entries for %s\n", pruneCount, c.Name()) + if pruneCount == 1 { + fmt.Fprintf(out, "Removed 1 duplicate entry for %s.\n", c.Name()) + } else if pruneCount > 1 { + fmt.Fprintf(out, "Removed %d duplicate entries for %s.\n", pruneCount, c.Name()) + } return true, nil } return false, nil diff --git a/internal/app/util.go b/internal/app/util.go index be9e36a..d0dd4c9 100644 --- a/internal/app/util.go +++ b/internal/app/util.go @@ -55,6 +55,9 @@ func ToggleRepo(r *CoprRepo, fs afero.Fs, out io.Writer, desiredState RepoState) repoFile := r.LocalFilePath() contents, err := afero.ReadFile(fs, repoFile) if err != nil { + if errors.Is(err, afero.ErrFileNotFound) { + return fmt.Errorf("repository %s/%s is not installed", r.User, r.Project) + } return err } fileLines := strings.Split(string(contents), "\n") @@ -134,7 +137,6 @@ func GetAllRepos(fs afero.Fs, out io.Writer) ([]*CoprRepo, error) { for scanner.Scan() { if strings.Contains(scanner.Text(), "[copr:copr") { t := strings.Split(strings.Trim(scanner.Text(), "[]"), ":") - // r, _ := app.NewCoprRepo(t[len(t)-2] + "/" + t[len(t)-1]) repoName := t[len(t)-2] + "/" + t[len(t)-1] if !slices.Contains(reposStrings, repoName) { r, err := NewCoprRepo(repoName)