diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 93cab7c..1e5b871 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -4,9 +4,7 @@ _Before opening an issue, search for similar bug reports or feature requests on **System info:** -* **Version used (`bop -v`):** -* **OS (e.g. from `/etc/*-release`):** -* **Kernel (`uname -a`):** +* **Verbose version info (`bop -vv`):** * **Install tools:** **Steps to reproduce:** diff --git a/README.md b/README.md index 333aa3a..3232596 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@

-

InstallationDocker supportCommand-line completionUsageBuild StatusContributingLicense

+

InstallationDocker supportCommand-line completionMan documentationUsageBuild StatusContributingLicense


@@ -64,6 +64,14 @@ Fish: sudo bop --completion=fish 1> /usr/share/fish/vendor_completions.d/bop.fish ``` +### Man documentation + +You can generate man page using next command: + +```bash +bop --generate-man | sudo gzip > /usr/share/man/man1/bop.1.gz +``` + ### Usage ``` @@ -79,9 +87,14 @@ Options Examples - bop redis redis*.rpm - Generate tests for Redis package + bop htop htop*.rpm + Generate simple tests for package + + bop redis redis*.rpm -s redis + Generate tests with service check + bop -o zl.recipe zlib zlib*.rpm minizip*.rpm + Generate tests with custom name ``` ### Build Status diff --git a/bop.go b/bop.go index d6c9a12..427da24 100644 --- a/bop.go +++ b/bop.go @@ -5,17 +5,27 @@ package main // ////////////////////////////////////////////////////////////////////////////////// // // // -// Copyright (c) 2022 ESSENTIAL KAOS // +// Copyright (c) 2023 ESSENTIAL KAOS // // Apache License, Version 2.0 // // // // ////////////////////////////////////////////////////////////////////////////////// // import ( + _ "embed" + CLI "github.com/essentialkaos/bop/cli" ) // ////////////////////////////////////////////////////////////////////////////////// // +//go:embed go.mod +var gomod []byte + +// gitrev is short hash of the latest git commit +var gitrev string + +// ////////////////////////////////////////////////////////////////////////////////// // + func main() { - CLI.Init() + CLI.Init(gitrev, gomod) } diff --git a/cli/cli.go b/cli/cli.go index 2568c0f..de66c5a 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -2,7 +2,7 @@ package cli // ////////////////////////////////////////////////////////////////////////////////// // // // -// Copyright (c) 2022 ESSENTIAL KAOS // +// Copyright (c) 2023 ESSENTIAL KAOS // // Apache License, Version 2.0 // // // // ////////////////////////////////////////////////////////////////////////////////// // @@ -24,11 +24,13 @@ import ( "github.com/essentialkaos/ek/v12/usage/completion/bash" "github.com/essentialkaos/ek/v12/usage/completion/fish" "github.com/essentialkaos/ek/v12/usage/completion/zsh" + "github.com/essentialkaos/ek/v12/usage/man" "github.com/essentialkaos/ek/v12/usage/update" "github.com/essentialkaos/bop/extractor" "github.com/essentialkaos/bop/generator" "github.com/essentialkaos/bop/rpm" + "github.com/essentialkaos/bop/support" ) // ////////////////////////////////////////////////////////////////////////////////// // @@ -36,7 +38,7 @@ import ( // App info const ( APP = "bop" - VER = "1.1.6" + VER = "1.2.0" DESC = "Utility for generating formal bibop tests for RPM packages" ) @@ -50,7 +52,9 @@ const ( OPT_HELP = "h:help" OPT_VER = "v:version" - OPT_COMPLETION = "completion" + OPT_VERB_VER = "vv:verbose-version" + OPT_COMPLETION = "completion" + OPT_GENERATE_MAN = "generate-man" ) // ////////////////////////////////////////////////////////////////////////////////// // @@ -62,13 +66,15 @@ var optMap = options.Map{ OPT_HELP: {Type: options.BOOL, Alias: "u:usage"}, OPT_VER: {Type: options.BOOL, Alias: "ver"}, - OPT_COMPLETION: {}, + OPT_VERB_VER: {Type: options.BOOL}, + OPT_COMPLETION: {}, + OPT_GENERATE_MAN: {Type: options.BOOL}, } // ////////////////////////////////////////////////////////////////////////////////// // // Init is main function -func Init() { +func Init(gitRev string, gomod []byte) { args, errs := options.Parse(optMap) if len(errs) != 0 { @@ -83,16 +89,18 @@ func Init() { fmtc.DisableColors = true } - if options.Has(OPT_COMPLETION) { - genCompletion() - } - - if options.GetB(OPT_VER) { - showAbout() + switch { + case options.Has(OPT_COMPLETION): + os.Exit(genCompletion()) + case options.Has(OPT_GENERATE_MAN): + os.Exit(genMan()) + case options.GetB(OPT_VER): + showAbout(gitRev) return - } - - if options.GetB(OPT_HELP) || len(args) < 2 { + case options.GetB(OPT_VERB_VER): + support.ShowSupportInfo(APP, VER, gitRev, gomod) + return + case options.GetB(OPT_HELP) || len(args) < 2: showUsage() return } @@ -205,23 +213,13 @@ func showUsage() { genUsage().Render() } -// genUsage generates usage info -func genUsage() *usage.Info { - info := usage.NewInfo("", "name", "package…") - - info.AddOption(OPT_OUTPUT, "Output file", "file") - info.AddOption(OPT_SERVICE, "List of services for checking {c}(mergable){!}", "service") - info.AddOption(OPT_NO_COLOR, "Disable colors in output") - info.AddOption(OPT_HELP, "Show this help message") - info.AddOption(OPT_VER, "Show version") - - info.AddExample("redis redis*.rpm", "Generate tests for Redis package") - - return info +// showAbout prints info about version +func showAbout(gitRev string) { + genAbout(gitRev).Render() } // genCompletion generates completion for different shells -func genCompletion() { +func genCompletion() int { info := genUsage() switch options.GetS(OPT_COMPLETION) { @@ -232,15 +230,44 @@ func genCompletion() { case "zsh": fmt.Printf(zsh.Generate(info, optMap, "bop")) default: - os.Exit(1) + return 1 } - os.Exit(0) + return 0 } -// showAbout prints info about version -func showAbout() { - about := &usage.About{ +// genMan generates man page +func genMan() int { + fmt.Println( + man.Generate( + genUsage(), + genAbout(""), + ), + ) + + return 0 +} + +// genUsage generates usage info +func genUsage() *usage.Info { + info := usage.NewInfo("", "name", "package…") + + info.AddOption(OPT_OUTPUT, "Output file", "file") + info.AddOption(OPT_SERVICE, "List of services for checking {c}(mergable){!}", "service") + info.AddOption(OPT_NO_COLOR, "Disable colors in output") + info.AddOption(OPT_HELP, "Show this help message") + info.AddOption(OPT_VER, "Show version") + + info.AddExample("htop htop*.rpm", "Generate simple tests for package") + info.AddExample("redis redis*.rpm -s redis", "Generate tests with service check") + info.AddExample("-o zl.recipe zlib zlib*.rpm minizip*.rpm", "Generate tests with custom name") + + return info +} + +// genAbout generates info about version +func genAbout(gitRev string) *usage.About { + return &usage.About{ App: APP, Version: VER, Desc: DESC, @@ -249,6 +276,4 @@ func showAbout() { License: "Apache License, Version 2.0 ", UpdateChecker: usage.UpdateChecker{"essentialkaos/bop", update.GitHubChecker}, } - - about.Render() } diff --git a/data/data.go b/data/data.go index fa4cb3f..9fd99ad 100644 --- a/data/data.go +++ b/data/data.go @@ -2,7 +2,7 @@ package data // ////////////////////////////////////////////////////////////////////////////////// // // // -// Copyright (c) 2022 ESSENTIAL KAOS // +// Copyright (c) 2023 ESSENTIAL KAOS // // Apache License, Version 2.0 // // // // ////////////////////////////////////////////////////////////////////////////////// // diff --git a/extractor/extractor.go b/extractor/extractor.go index 1de9673..d642fac 100644 --- a/extractor/extractor.go +++ b/extractor/extractor.go @@ -2,7 +2,7 @@ package extractor // ////////////////////////////////////////////////////////////////////////////////// // // // -// Copyright (c) 2022 ESSENTIAL KAOS // +// Copyright (c) 2023 ESSENTIAL KAOS // // Apache License, Version 2.0 // // // // ////////////////////////////////////////////////////////////////////////////////// // diff --git a/generator/generator.go b/generator/generator.go index b4848ac..a977cb3 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -2,7 +2,7 @@ package generator // ////////////////////////////////////////////////////////////////////////////////// // // // -// Copyright (c) 2022 ESSENTIAL KAOS // +// Copyright (c) 2023 ESSENTIAL KAOS // // Apache License, Version 2.0 // // // // ////////////////////////////////////////////////////////////////////////////////// // diff --git a/go.mod b/go.mod index f065838..6c67dda 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,9 @@ module github.com/essentialkaos/bop go 1.17 -require github.com/essentialkaos/ek/v12 v12.57.1 +require ( + github.com/essentialkaos/depsy v1.0.0 + github.com/essentialkaos/ek/v12 v12.57.1 +) -require golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect +require golang.org/x/sys v0.4.0 // indirect diff --git a/go.sum b/go.sum index 8cbe5e3..a2a6f58 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/essentialkaos/check v1.3.0 h1:ria+8o22RCLdt2D/1SHQsEH5Mmy5S+iWHaGHrrbPUc0= github.com/essentialkaos/check v1.3.0/go.mod h1:PhxzfJWlf5L/skuyhzBLIvjMB5Xu9TIyDIsqpY5MvB8= +github.com/essentialkaos/depsy v1.0.0 h1:FikBtTnNhk+xFO/hFr+CfiKs6QnA3wMD6tGL0XTEUkc= +github.com/essentialkaos/depsy v1.0.0/go.mod h1:XVsB2eVUonEzmLKQP3ig2P6v2+WcHVgJ10zm0JLqFMM= github.com/essentialkaos/ek/v12 v12.57.1 h1:9dj32HLCVmseBoa43F6HaZz1qbKts5GNBCtFdrpQPno= github.com/essentialkaos/ek/v12 v12.57.1/go.mod h1:G8ghiSKh8ToJQCdB2bAhE3CnI6dn9nTJdWH3bQIVr1U= github.com/essentialkaos/go-linenoise/v3 v3.4.0/go.mod h1:t1kNLY2bSMQCy1JXOefD2BDLs/TTPMtTv3DFNV5uDSI= @@ -20,8 +22,9 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI= golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/rpm/rpm.go b/rpm/rpm.go index be603ab..f92119a 100644 --- a/rpm/rpm.go +++ b/rpm/rpm.go @@ -2,7 +2,7 @@ package rpm // ////////////////////////////////////////////////////////////////////////////////// // // // -// Copyright (c) 2022 ESSENTIAL KAOS // +// Copyright (c) 2023 ESSENTIAL KAOS // // Apache License, Version 2.0 // // // // ////////////////////////////////////////////////////////////////////////////////// // diff --git a/support/support.go b/support/support.go new file mode 100644 index 0000000..29e6ae6 --- /dev/null +++ b/support/support.go @@ -0,0 +1,146 @@ +package support + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "fmt" + "os" + "runtime" + "strings" + + "github.com/essentialkaos/ek/v12/fmtc" + "github.com/essentialkaos/ek/v12/fmtutil" + "github.com/essentialkaos/ek/v12/hash" + "github.com/essentialkaos/ek/v12/strutil" + + "github.com/essentialkaos/depsy" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +// Pkg contains basic package info +type Pkg struct { + Name string + Version string +} + +// Pkgs is slice with packages +type Pkgs []Pkg + +// ////////////////////////////////////////////////////////////////////////////////// // + +// ShowSupportInfo prints verbose info about application, system, dependencies and +// important environment +func ShowSupportInfo(app, ver, gitRev string, gomod []byte) { + pkgs := collectEnvInfo() + + fmtutil.SeparatorTitleColorTag = "{s-}" + fmtutil.SeparatorFullscreen = false + fmtutil.SeparatorColorTag = "{s-}" + fmtutil.SeparatorSize = 80 + + showApplicationInfo(app, ver, gitRev) + showOSInfo() + showEnvInfo(pkgs) + showDepsInfo(gomod) + + fmtutil.Separator(false) +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// showApplicationInfo shows verbose information about application +func showApplicationInfo(app, ver, gitRev string) { + fmtutil.Separator(false, "APPLICATION INFO") + + printInfo(7, "Name", app) + printInfo(7, "Version", ver) + + printInfo(7, "Git SHA", fmtc.Sprintf( + "%s {s}(%s/%s){!}", + strings.TrimLeft(runtime.Version(), "go"), + runtime.GOOS, runtime.GOARCH, + )) + + if gitRev != "" { + if !fmtc.DisableColors && fmtc.IsTrueColorSupported() { + printInfo(7, "Git SHA", gitRev+getHashColorBullet(gitRev)) + } else { + printInfo(7, "Git SHA", gitRev) + } + } + + bin, _ := os.Executable() + binSHA := hash.FileHash(bin) + + if binSHA != "" { + binSHA = strutil.Head(binSHA, 7) + if !fmtc.DisableColors && fmtc.IsTrueColorSupported() { + printInfo(7, "Bin SHA", binSHA+getHashColorBullet(binSHA)) + } else { + printInfo(7, "Bin SHA", binSHA) + } + } +} + +// showDepsInfo shows information about all dependencies +func showDepsInfo(gomod []byte) { + deps := depsy.Extract(gomod, false) + + if len(deps) == 0 { + return + } + + fmtutil.Separator(false, "DEPENDENCIES") + + for _, dep := range deps { + if dep.Extra == "" { + fmtc.Printf(" {s}%8s{!} %s\n", dep.Version, dep.Path) + } else { + fmtc.Printf(" {s}%8s{!} %s {s-}(%s){!}\n", dep.Version, dep.Path, dep.Extra) + } + } +} + +// getHashColorBullet return bullet with color from hash +func getHashColorBullet(v string) string { + if len(v) > 6 { + v = strutil.Head(v, 6) + } + + return fmtc.Sprintf(" {#" + strutil.Head(v, 6) + "}● {!}") +} + +// printInfo formats and prints info record +func printInfo(size int, name, value string) { + name = name + ":" + size++ + + if value == "" { + fm := fmt.Sprintf(" {*}%%-%ds{!} {s-}—{!}\n", size) + fmtc.Printf(fm, name) + } else { + fm := fmt.Sprintf(" {*}%%-%ds{!} %%s\n", size) + fmtc.Printf(fm, name, value) + } +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// getMaxSize returns max package name size +func (p Pkgs) getMaxSize() int { + size := 0 + + for _, pkg := range p { + if len(pkg.Name) > size { + size = len(pkg.Name) + } + } + + return size +} diff --git a/support/support_linux.go b/support/support_linux.go new file mode 100644 index 0000000..1bb41af --- /dev/null +++ b/support/support_linux.go @@ -0,0 +1,130 @@ +package support + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2023 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "os/exec" + "strings" + + "github.com/essentialkaos/ek/v12/fmtc" + "github.com/essentialkaos/ek/v12/fmtutil" + "github.com/essentialkaos/ek/v12/fsutil" + "github.com/essentialkaos/ek/v12/system" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +// showOSInfo shows verbose information about system +func showOSInfo() { + osInfo, err := system.GetOSInfo() + + if err == nil { + fmtutil.Separator(false, "OS INFO") + + printInfo(12, "Name", osInfo.Name) + printInfo(12, "Pretty Name", osInfo.PrettyName) + printInfo(12, "Version", osInfo.VersionID) + printInfo(12, "ID", osInfo.ID) + printInfo(12, "ID Like", osInfo.IDLike) + printInfo(12, "Version ID", osInfo.VersionID) + printInfo(12, "Version Code", osInfo.VersionCodename) + printInfo(12, "CPE", osInfo.CPEName) + } + + systemInfo, err := system.GetSystemInfo() + + if err != nil { + return + } else { + if osInfo == nil { + fmtutil.Separator(false, "SYSTEM INFO") + printInfo(12, "Name", systemInfo.OS) + printInfo(12, "Version", systemInfo.Version) + } + } + + printInfo(12, "Arch", systemInfo.Arch) + printInfo(12, "Kernel", systemInfo.Kernel) + + containerEngine := "No" + + switch { + case fsutil.IsExist("/.dockerenv"): + containerEngine = "Yes (Docker)" + case fsutil.IsExist("/run/.containerenv"): + containerEngine = "Yes (Podman)" + } + + fmtc.NewLine() + + printInfo(12, "Container", containerEngine) +} + +// showEnvInfo shows info about environment +func showEnvInfo(pkgs Pkgs) { + fmtutil.Separator(false, "ENVIRONMENT") + + size := pkgs.getMaxSize() + + for _, pkg := range pkgs { + printInfo(size, pkg.Name, pkg.Version) + } +} + +// collectEnvInfo collects info about packages +func collectEnvInfo() Pkgs { + return Pkgs{ + getPackageInfo("rpm"), + } +} + +// getPackageVersion returns package name from rpm database +func getPackageInfo(name string) Pkg { + switch { + case isDEBBased(): + return getDEBPackageInfo(name) + case isRPMBased(): + return getRPMPackageInfo(name) + } + + return Pkg{name, ""} +} + +// isDEBBased returns true if is DEB-based distro +func isRPMBased() bool { + return fsutil.IsExist("/usr/bin/rpm") +} + +// isDEBBased returns true if is DEB-based distro +func isDEBBased() bool { + return fsutil.IsExist("/usr/bin/dpkg-query") +} + +// getRPMPackageInfo returns info about RPM package +func getRPMPackageInfo(name string) Pkg { + cmd := exec.Command("rpm", "-q", name) + out, err := cmd.Output() + + if err != nil || len(out) == 0 { + return Pkg{name, ""} + } + + return Pkg{name, strings.TrimRight(string(out), "\n\r")} +} + +// getDEBPackageInfo returns info about DEB package +func getDEBPackageInfo(name string) Pkg { + cmd := exec.Command("dpkg-query", "--showformat=${Version}", "--show", name) + out, err := cmd.Output() + + if err != nil || len(out) == 0 { + return Pkg{name, ""} + } + + return Pkg{name, string(out)} +}