Skip to content

Commit

Permalink
feat : generate manpages for cli during crc setup (#4181)
Browse files Browse the repository at this point in the history
+ crc should generate manpages for all sub commands and place them
  in `~/.local/share/man/man1` directory
+ These man pages would only be generated once, crc would skip generation
  if it detects man pages already present in target directory
+ These generated man pages would be cleaned up during `crc cleanup`

Signed-off-by: Rohan Kumar <rohaan@redhat.com>
  • Loading branch information
rohanKanojia committed Jan 27, 2025
1 parent 9537264 commit 080022d
Show file tree
Hide file tree
Showing 32 changed files with 9,965 additions and 4 deletions.
10 changes: 10 additions & 0 deletions cmd/crc/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import (
"strings"
"time"

"github.com/crc-org/crc/v2/pkg/crc/adminhelper"
"github.com/spf13/cobra/doc"

cmdBundle "github.com/crc-org/crc/v2/cmd/crc/cmd/bundle"
cmdConfig "github.com/crc-org/crc/v2/cmd/crc/cmd/config"
crcConfig "github.com/crc-org/crc/v2/pkg/crc/config"
Expand Down Expand Up @@ -112,6 +115,9 @@ func Execute() {
}
os.Exit(defaultErrorExitCode)
}
if err := adminhelper.GenerateManPages(crcManPageGenerator, constants.CrcManPageDir); err != nil {
os.Exit(defaultErrorExitCode)
}
runPostrun()
}

Expand Down Expand Up @@ -186,3 +192,7 @@ func attachMiddleware(names []string, cmd *cobra.Command) {
cmd.RunE = executeWithLogging(fullCmd, src)
}
}

func crcManPageGenerator(targetDir string) error {
return doc.GenManTree(rootCmd, adminhelper.CrcManPageHeader, targetDir)
}
46 changes: 46 additions & 0 deletions cmd/crc/cmd/root_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cmd

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func TestCrcManPageGenerator_WhenInvoked_GeneratesManPagesForAllCrcSubCommands(t *testing.T) {
// Given
dir := t.TempDir()

// When
err := crcManPageGenerator(dir)

// Then
assert.NoError(t, err)
files, readErr := os.ReadDir(dir)
assert.NoError(t, readErr)
var manPagesFiles []string
for _, manPage := range files {
manPagesFiles = append(manPagesFiles, manPage.Name())
}
assert.ElementsMatch(t, []string{
"crc-bundle-generate.1",
"crc-bundle.1",
"crc-cleanup.1",
"crc-config-get.1",
"crc-config-set.1",
"crc-config-unset.1",
"crc-config-view.1",
"crc-config.1",
"crc-console.1",
"crc-delete.1",
"crc-ip.1",
"crc-oc-env.1",
"crc-podman-env.1",
"crc-setup.1",
"crc-start.1",
"crc-status.1",
"crc-stop.1",
"crc-version.1",
"crc.1",
}, manPagesFiles)
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ require (
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
github.com/containers/ocicrypt v1.2.0 // indirect
github.com/containers/storage v1.55.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/creack/pty v1.1.18 // indirect
github.com/cucumber/gherkin/go/v26 v26.2.0 // indirect
github.com/cucumber/messages/go/v21 v21.0.1 // indirect
Expand Down Expand Up @@ -164,6 +165,7 @@ require (
github.com/qdm12/dns/v2 v2.0.0-rc6 // indirect
github.com/qdm12/gosettings v0.4.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ github.com/containers/storage v1.55.1/go.mod h1:28cB81IDk+y7ok60Of6u52RbCeBRucbF
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/crc-org/admin-helper v0.5.4 h1:Wq6wp6514MipPHHYdoL2VUyhUL9qh26wR1I3qPaVxf4=
github.com/crc-org/admin-helper v0.5.4/go.mod h1:sFkqIILzKrt62CH1bJn5PSBFSdhaCyMdz6BG37N3TBE=
Expand Down Expand Up @@ -349,6 +350,7 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
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=
Expand Down
158 changes: 158 additions & 0 deletions pkg/crc/adminhelper/manpages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package adminhelper

import (
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strings"

"github.com/spf13/cobra/doc"

"github.com/crc-org/crc/v2/pkg/crc/logging"
)

var (
rootCrcManPage = "crc.1.gz"
operatingSystem = runtime.GOOS
osEnvGetter = os.Getenv
osEnvSetter = os.Setenv
CrcManPageHeader = &doc.GenManHeader{
Title: "CRC",
Section: "1",
}
)

func GenerateManPages(manPageGenerator func(targetDir string) error, targetDir string) error {
manUserCommandTargetFolder := filepath.Join(targetDir, "man1")
if shouldGenerateManPages(manUserCommandTargetFolder) {
if _, err := os.Stat(manUserCommandTargetFolder); os.IsNotExist(err) {
err = os.MkdirAll(manUserCommandTargetFolder, 0755)
if err != nil {
logging.Errorf("error in creating dir for man pages: %s", err.Error())
}
}
temporaryManPagesDir, err := generateManPagesInTemporaryDirectory(manPageGenerator)
if err != nil {
return err
}
err = compressManPages(temporaryManPagesDir, manUserCommandTargetFolder)
if err != nil {
return fmt.Errorf("error in compressing man pages: %s", err.Error())
}
err = updateManPathEnvironmentVariable(targetDir)
if err != nil {
return fmt.Errorf("error updating MANPATH environment variable: %s", err.Error())
}
err = os.RemoveAll(temporaryManPagesDir)
if err != nil {
return fmt.Errorf("error removing temporary man pages directory: %s", err.Error())
}
}
return nil
}

func updateManPathEnvironmentVariable(folder string) error {
manPath := osEnvGetter("MANPATH")
if !manPathAlreadyContains(folder, manPath) {
if manPath == "" {
manPath = folder
} else {
manPath = fmt.Sprintf("%s%c%s", manPath, os.PathListSeparator, folder)
}
err := osEnvSetter("MANPATH", manPath)
if err != nil {
return err
}
}

return nil
}

func manPathAlreadyContains(manPathEnvVarValue string, folder string) bool {
manDirs := strings.Split(manPathEnvVarValue, string(os.PathListSeparator))
for _, manDir := range manDirs {
if manDir == folder {
return true
}
}
return false
}

func generateManPagesInTemporaryDirectory(manPageGenerator func(targetDir string) error) (string, error) {
tempDir, err := os.MkdirTemp("", "crc-manpages")
if err != nil {
return "", err
}
manPagesGenerationErr := manPageGenerator(tempDir)
if manPagesGenerationErr != nil {
return "", manPagesGenerationErr
}
logging.Debugf("Successfully generated manpages in %s", tempDir)
return tempDir, nil
}

func compressManPages(manPagesSourceFolder string, manPagesTargetFolder string) error {
return filepath.Walk(manPagesSourceFolder, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

if info.IsDir() {
return nil
}

srcFile, err := os.Open(path)
if err != nil {
return err
}
defer srcFile.Close()

compressedFilePath := filepath.Join(manPagesTargetFolder, info.Name()+".gz")
compressedFile, err := os.Create(compressedFilePath)
if err != nil {
return err
}
defer compressedFile.Close()

gzipWriter := gzip.NewWriter(compressedFile)
defer gzipWriter.Close()

_, err = io.Copy(gzipWriter, srcFile)
if err != nil {
return err
}
return nil
})
}

func manPagesAlreadyGenerated(manPagesTargetFolder string) bool {
rootCrcManPageFilePath := filepath.Join(manPagesTargetFolder, rootCrcManPage)
if _, err := os.Stat(rootCrcManPageFilePath); os.IsNotExist(err) {
return false
}
return true
}

func shouldGenerateManPages(manUserCommandTargetFolder string) bool {
return operatingSystem != "windows" && !manPagesAlreadyGenerated(manUserCommandTargetFolder)
}

func RemoveCrcManPages(manPageDir string) error {
manUserCommandTargetFolder := filepath.Join(manPageDir, "man1")
err := filepath.Walk(manUserCommandTargetFolder, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && filepath.Base(path)[:len("crc")] == "crc" {
err = os.Remove(path)
if err != nil {
return err
}
}
return nil
})
return err
}
Loading

0 comments on commit 080022d

Please sign in to comment.