Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 72 additions & 72 deletions NOTICE

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,14 @@ func newAppContext() *appContext {
}

var configCmd = &cobra.Command{
Use: "config",
Short: "Launch the interactive configuration tool.",
Long: "Starts an interactive terminal-based UI to generate your Cloudfuse configuration file.",
Use: "config",
Short: "Launch the interactive configuration tool.",
Long: "Starts an interactive terminal-based UI to generate your Cloudfuse configuration file.",
Aliases: []string{"configure", "cfg"},
GroupID: groupConfig,
Args: cobra.NoArgs,
Example: ` # Launch the interactive configuration wizard
cloudfuse config`,
RunE: func(cmd *cobra.Command, args []string) error {
tui := newAppContext()
if err := tui.run(); err != nil {
Expand Down
17 changes: 12 additions & 5 deletions cmd/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,23 @@ var docCmd = &cobra.Command{
Use: "doc",
Hidden: true,
Short: "Generates documentation for the tool in Markdown format",
Long: "Generates documentation for the tool in Markdown format, and stores them in the designated location",
Long: "Generates Markdown documentation for all cloudfuse commands.\nOutputs one file per command to the specified location.",
Args: cobra.NoArgs,
Example: ` # Generate docs to default location
cloudfuse doc

# Generate docs to custom directory
cloudfuse doc --output-location=/path/to/docs`,
RunE: func(cmd *cobra.Command, args []string) error {
// verify the output location
f, err := os.Stat(docCmdInput.outputLocation)
if err != nil && os.IsNotExist(err) {
// create the output location if it does not exist yet
if err = os.MkdirAll(docCmdInput.outputLocation, os.ModePerm); err != nil {
return fmt.Errorf("failed to create output location [%s]", err.Error())
return fmt.Errorf("failed to create output location: %w", err)
}
} else if err != nil {
return fmt.Errorf("cannot access output location [%s]", err.Error())
return fmt.Errorf("cannot access output location: %w", err)
} else if !f.IsDir() {
return fmt.Errorf("output location is invalid as it is pointing to a file")
}
Expand All @@ -62,8 +68,8 @@ var docCmd = &cobra.Command{
err = doc.GenMarkdownTree(rootCmd, docCmdInput.outputLocation)
if err != nil {
return fmt.Errorf(
"cannot generate command tree [%s]. Please contact the dev team",
err.Error(),
"cannot generate command tree: %w",
err,
)
}
return nil
Expand All @@ -74,4 +80,5 @@ func init() {
rootCmd.AddCommand(docCmd)
docCmd.PersistentFlags().StringVar(&docCmdInput.outputLocation, "output-location", "./doc",
"where to put the generated markdown files")
_ = docCmd.MarkPersistentFlagDirname("output-location")
}
22 changes: 22 additions & 0 deletions cmd/doc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,28 @@ func (suite *docTestSuite) TestOutputDirIsFileError() {
suite.assert.Contains(op, "output location is invalid as it is pointing to a file")
}

// TestDocHelp tests doc command help output
func (suite *docTestSuite) TestDocHelp() {
defer suite.cleanupTest()

op, err := executeCommandC(rootCmd, "doc", "--help")
suite.assert.NoError(err)
suite.assert.Contains(op, "Generates Markdown documentation")
suite.assert.Contains(op, "output-location")
}

// TestDocNoArgs tests doc command without args (should still work with defaults)
func (suite *docTestSuite) TestDocNoArgs() {
defer suite.cleanupTest()

// Create temp dir for default output
opDir := "/tmp/docs_" + randomString(6)
defer os.RemoveAll(opDir)

_, err := executeCommandC(rootCmd, "doc", fmt.Sprintf("--output-location=%s", opDir))
suite.assert.NoError(err)
}

func TestDocCommand(t *testing.T) {
suite.Run(t, new(docTestSuite))
}
4 changes: 3 additions & 1 deletion cmd/gen-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ var optsGenCfg genConfigParams
var generatedConfig = &cobra.Command{
Use: "gen-config",
Short: "Generate config file from template.",
Long: "Generate config file from template.",
Long: "Generate a cloudfuse configuration file from a template.\nReplaces placeholder values with provided parameters.",
SuggestFor: []string{"generate default config", "generate config"},
Hidden: true,
Args: cobra.ExactArgs(0),
Example: ` # Generate config from template
cloudfuse gen-config --config-file=template.yaml --output-file=config.yaml --temp-path=/tmp/cloudfuse`,
RunE: func(cmd *cobra.Command, args []string) error {
var templateConfig []byte

Expand Down
24 changes: 24 additions & 0 deletions cmd/gen-config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,30 @@ func (suite *genConfig) TestNoPath() {
suite.assert.Error(err)
}

// TestGenConfigHelp tests the help output
func (suite *genConfig) TestGenConfigHelp() {
defer suite.cleanupTest()

output, err := executeCommandC(rootCmd, "gen-config", "--help")
suite.assert.NoError(err)
suite.assert.Contains(output, "gen-config")
suite.assert.Contains(output, "temp-path")
suite.assert.Contains(output, "config-file")
}

// TestValidateGenConfigOptionsInvalidConfigFile tests validation with invalid config file
func (suite *genConfig) TestValidateGenConfigOptionsInvalidConfigFile() {
defer suite.cleanupTest()

_, err := executeCommandC(
rootCmd,
"gen-config",
"--config-file=/nonexistent/path/config.yaml",
"--temp-path=/tmp",
)
suite.assert.Error(err)
}

func TestGenConfig(t *testing.T) {
suite.Run(t, new(genConfig))
}
3 changes: 2 additions & 1 deletion cmd/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ var generateCmd = &cobra.Command{
Use: "generate <component name>",
Hidden: true,
Short: "Generate a new component for Cloudfuse",
Long: "Generate a new component for Cloudfuse",
Long: "Generate a new cloudfuse component with boilerplate code.\nRuns the componentGenerator.sh script to scaffold the component structure.",
SuggestFor: []string{"gen", "gener"},
Args: cobra.ExactArgs(1),
Example: " cloudfuse generate mycomponent",
RunE: func(cmd *cobra.Command, args []string) error {
componentName := args[0]
script := exec.Command("./cmd/componentGenerator.sh", componentName)
Expand Down
74 changes: 74 additions & 0 deletions cmd/generator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
Licensed under the MIT License <http://opensource.org/licenses/MIT>.

Copyright © 2023-2025 Seagate Technology LLC and/or its Affiliates
Copyright © 2020-2025 Microsoft Corporation. All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
*/

package cmd

import (
"testing"

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

type generatorTestSuite struct {
suite.Suite
assert *assert.Assertions
}

func (suite *generatorTestSuite) SetupTest() {
suite.assert = assert.New(suite.T())
}

func (suite *generatorTestSuite) cleanupTest() {
resetCLIFlags(*generateCmd)
}

func TestGeneratorCommand(t *testing.T) {
suite.Run(t, new(generatorTestSuite))
}

// TestGeneratorRequiresArg tests that generate command requires exactly one argument
func (suite *generatorTestSuite) TestGeneratorRequiresArg() {
defer suite.cleanupTest()

output, _ := executeCommandC(rootCmd, "generate")
suite.assert.Contains(output, "accepts 1 arg(s)")
}

// TestGeneratorIsHidden tests that the generate command is hidden
func (suite *generatorTestSuite) TestGeneratorIsHidden() {
defer suite.cleanupTest()

suite.assert.True(generateCmd.Hidden, "generate command should be hidden")
}

// TestGeneratorHelp tests that help is displayed correctly
func (suite *generatorTestSuite) TestGeneratorHelp() {
defer suite.cleanupTest()

output, _ := executeCommandC(rootCmd, "generate", "--help")
suite.assert.Contains(output, "Generate a new cloudfuse component")
suite.assert.Contains(output, "cloudfuse generate mycomponent")
}
15 changes: 8 additions & 7 deletions cmd/health-monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func resetMonitorOptions() {
var healthMonCmd = &cobra.Command{
Use: "health-monitor",
Short: "Monitor cloudfuse mount",
Long: "Monitor cloudfuse mount",
Long: "Monitor a cloudfuse mount point for health and performance.\nThis command is typically spawned by the mount command when health monitoring is enabled.",
SuggestFor: []string{"cfusemon", "monitor health"},
Args: cobra.ExactArgs(0),
Hidden: true,
Expand All @@ -72,14 +72,14 @@ var healthMonCmd = &cobra.Command{
err := validateHMonOptions()
if err != nil {
log.Err("health-monitor : failed to validate options [%s]", err.Error())
return fmt.Errorf("failed to validate options [%s]", err.Error())
return fmt.Errorf("failed to validate options: %w", err)
}

options.ConfigFile = configFile
err = parseConfig()
if err != nil {
log.Err("health-monitor : failed to parse config [%s]", err.Error())
return err
return fmt.Errorf("failed to parse config: %w", err)
}

err = config.UnmarshalKey("file_cache", &cacheMonitorOptions)
Expand All @@ -88,7 +88,7 @@ var healthMonCmd = &cobra.Command{
"health-monitor : file_cache config error (invalid config attributes) [%s]",
err.Error(),
)
return fmt.Errorf("invalid file_cache config [%s]", err.Error())
return fmt.Errorf("invalid file_cache config: %w", err)
}

err = config.UnmarshalKey("health_monitor", &options.MonitorOpt)
Expand All @@ -97,7 +97,7 @@ var healthMonCmd = &cobra.Command{
"health-monitor : health_monitor config error (invalid config attributes) [%s]",
err.Error(),
)
return fmt.Errorf("invalid health_monitor config [%s]", err.Error())
return fmt.Errorf("invalid health_monitor config: %w", err)
}

cliParams := buildCliParamForMonitor()
Expand All @@ -108,7 +108,7 @@ var healthMonCmd = &cobra.Command{
if runtime.GOOS == "windows" {
path, err := filepath.Abs(hmcommon.CfuseMon + ".exe")
if err != nil {
return fmt.Errorf("failed to start health monitor [%s]", err.Error())
return fmt.Errorf("failed to start health monitor: %w", err)
}
hmcmd = exec.Command(path, cliParams...)
} else {
Expand All @@ -122,7 +122,7 @@ var healthMonCmd = &cobra.Command{
if err != nil {
common.EnableMonitoring = false
log.Err("health-monitor : failed to start health monitor [%s]", err.Error())
return fmt.Errorf("failed to start health monitor [%s]", err.Error())
return fmt.Errorf("failed to start health monitor: %w", err)
}

return nil
Expand Down Expand Up @@ -208,4 +208,5 @@ func init() {
healthMonCmd.Flags().StringVar(&configFile, "config-file", "config.yaml",
"Configures the path for the file where the account credentials are provided. Default is config.yaml")
_ = healthMonCmd.MarkFlagRequired("config-file")
_ = healthMonCmd.MarkFlagFilename("config-file", "yaml")
}
34 changes: 23 additions & 11 deletions cmd/health-monitor_stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,25 +38,40 @@ import (
var cloudfusePid string

var healthMonStop = &cobra.Command{
Use: "stop",
Short: "Stops the health monitor binary associated with a given Cloudfuse pid",
Long: "Stops the health monitor binary associated with a given Cloudfuse pid",
Use: "stop",
Short: "Stops health monitor binaries",
Long: `Stops health monitor binaries.

Use 'stop --pid=<cloudfuse-pid>' to stop a specific monitor,
or 'stop all' to stop all running monitors.`,
SuggestFor: []string{"stp", "st"},
Example: ` # Stop a specific health monitor by cloudfuse pid
cloudfuse health-monitor stop --pid=12345

# Stop all health monitors
cloudfuse health-monitor stop all`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cloudfusePid = strings.TrimSpace(cloudfusePid)

if len(cloudfusePid) == 0 {
return fmt.Errorf("pid of cloudfuse process not given")
return fmt.Errorf(
"pid of cloudfuse process not given. Use --pid flag or 'stop all' subcommand",
)
}

pid, err := getPid(cloudfusePid)
if err != nil {
return fmt.Errorf("failed to get health monitor pid")
return fmt.Errorf(
"failed to get health monitor pid for cloudfuse pid %s: %w",
cloudfusePid,
err,
)
}

err = stop(pid)
if err != nil {
return fmt.Errorf("failed to stop health monitor")
return fmt.Errorf("failed to stop health monitor: %w", err)
}

return nil
Expand Down Expand Up @@ -130,17 +145,14 @@ func stop(pid string) error {
_, err := cliOut.Output()
if err != nil {
return err
} else {
fmt.Println("Successfully stopped health monitor binary.")
return nil
}
fmt.Println("Successfully stopped health monitor binary.")
return nil
}

func init() {
healthMonCmd.AddCommand(healthMonStop)
healthMonStop.AddCommand(healthMonStopAll)

healthMonStop.Flags().
StringVar(&cloudfusePid, "pid", "", "Cloudfuse PID associated with the health monitor that should be stopped")
_ = healthMonStop.MarkFlagRequired("pid")
}
19 changes: 12 additions & 7 deletions cmd/health-monitor_stop_all.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,17 @@ import (
var healthMonStopAll = &cobra.Command{
Use: "all",
Short: "Stop all health monitor binaries",
Long: "Stop all health monitor binaries",
SuggestFor: []string{"al", "all"},
Long: "Stop all running cloudfuse health monitor processes.\nUses taskkill on Windows and killall on Linux.",
SuggestFor: []string{"al"},
Args: cobra.NoArgs,
Example: ` # Stop all health monitors
cloudfuse health-monitor stop all`,
RunE: func(cmd *cobra.Command, args []string) error {
err := stopAll()
if err != nil {
return fmt.Errorf("failed to stop all health monitor binaries [%s]", err.Error())
return fmt.Errorf("failed to stop all health monitor binaries: %w", err)
}
cmd.Println("Successfully stopped all health monitor binaries.")
return nil
},
}
Expand All @@ -57,15 +61,16 @@ func stopAll() error {
if err != nil {
return err
}
fmt.Println("Successfully stopped all health monitor binaries.")
return nil
}
cliOut := exec.Command("killall", hmcommon.CfuseMon)
_, err := cliOut.Output()
if err != nil {
return err
} else {
fmt.Println("Successfully stopped all health monitor binaries.")
return nil
}
return nil
}

func init() {
healthMonStop.AddCommand(healthMonStopAll)
}
Loading
Loading