cmd/root.go (123 lines of code) (raw):
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"log"
"os"
"strings"
"github.com/go-viper/mapstructure/v2"
"github.com/googlecloudplatform/gcsfuse/v2/cfg"
"github.com/googlecloudplatform/gcsfuse/v2/common"
"github.com/googlecloudplatform/gcsfuse/v2/internal/util"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
type mountFn func(c *cfg.Config, bucketName, mountPoint string) error
// newRootCmd accepts the mountFn that it executes with the parsed configuration
func newRootCmd(m mountFn) (*cobra.Command, error) {
var (
configObj cfg.Config
cfgFile string
cfgErr error
v = viper.New()
)
rootCmd := &cobra.Command{
Use: "gcsfuse [flags] bucket mount_point",
Short: "Mount a specified GCS bucket or all accessible buckets locally",
Long: `Cloud Storage FUSE is an open source FUSE adapter that lets you mount
and access Cloud Storage buckets as local file systems. For a technical overview
of Cloud Storage FUSE, see https://cloud.google.com/storage/docs/gcs-fuse.`,
Version: common.GetVersion(),
Args: cobra.RangeArgs(2, 3),
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if cfgErr != nil {
return fmt.Errorf("error while parsing config: %w", cfgErr)
}
bucket, mountPoint, err := populateArgs(args[1:])
if err != nil {
return fmt.Errorf("error occurred while extracting the bucket and mountPoint: %w", err)
}
return m(&configObj, bucket, mountPoint)
},
}
initConfig := func() {
if cfgFile != "" {
cfgFile, err := util.GetResolvedPath(cfgFile)
if err != nil {
cfgErr = fmt.Errorf("error while resolving config-file path[%s]: %w", cfgFile, err)
return
}
v.SetConfigFile(cfgFile)
v.SetConfigType("yaml")
if err := v.ReadInConfig(); err != nil {
cfgErr = fmt.Errorf("error while reading the config: %w", err)
return
}
}
if cfgErr = v.Unmarshal(&configObj, viper.DecodeHook(cfg.DecodeHook()), func(decoderConfig *mapstructure.DecoderConfig) {
// By default, viper supports mapstructure tags for unmarshalling. Override that to support yaml tag.
decoderConfig.TagName = "yaml"
// Reject the config file if any of the fields in the YAML don't map to the struct.
decoderConfig.ErrorUnused = true
},
); cfgErr != nil {
return
}
if cfgErr = cfg.ValidateConfig(v, &configObj); cfgErr != nil {
return
}
var optimizedFlags []string
if optimizedFlags, cfgErr = cfg.Optimize(&configObj, v); cfgErr != nil {
return
}
if cfgErr = cfg.Rationalize(v, &configObj, optimizedFlags); cfgErr != nil {
return
}
}
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, cfg.ConfigFileFlagName, "", "The path to the config file where all gcsfuse related config needs to be specified. "+
"Refer to 'https://cloud.google.com/storage/docs/gcsfuse-cli#config-file' for possible configurations.")
// Add all the other flags.
if err := cfg.BuildFlagSet(rootCmd.PersistentFlags()); err != nil {
return nil, fmt.Errorf("error while declaring flags: %w", err)
}
if err := cfg.BindFlags(v, rootCmd.PersistentFlags()); err != nil {
return nil, fmt.Errorf("error while binding flags: %w", err)
}
return rootCmd, nil
}
// convertToPosixArgs converts a slice of commandline args and transforms them
// into POSIX compliant args. All it does is that it converts flags specified
// using a single-hyphen to double-hyphens. We are excluding "-v" because it's
// reserved for showing version in Cobra.
func convertToPosixArgs(args []string, c *cobra.Command) []string {
pArgs := make([]string, 0, len(args))
flagSet := make(map[string]bool)
c.PersistentFlags().VisitAll(func(f *pflag.Flag) {
flagSet[f.Name] = true
})
// Treat help and version like flags
flagSet["version"] = true
flagSet["help"] = true
for _, a := range args {
switch {
case a == "--v", a == "-v":
pArgs = append(pArgs, "-v")
case a == "--h", a == "-h":
pArgs = append(pArgs, "-h")
case strings.HasPrefix(a, "-") && !strings.HasPrefix(a, "--"):
// Remove the string post the "=" sign.
// This converts -a=b to -a.
flg, _, _ := strings.Cut(a, "=")
// Remove one hyphen from the beginning.
// This converts -a -> a.
flg, _ = strings.CutPrefix(flg, "-")
if flagSet[flg] {
// "a" is a full-form flag which has been specified with a single hyphen.
// So add another hyphen so that pflag processes it correctly.
pArgs = append(pArgs, "-"+a)
} else {
// "a" is a flag so, keep it as is.
pArgs = append(pArgs, a)
}
default:
pArgs = append(pArgs, a)
}
}
return pArgs
}
var ExecuteMountCmd = func() {
rootCmd, err := newRootCmd(Mount)
if err != nil {
log.Fatalf("Error occurred while creating the root command: %v", err)
}
rootCmd.SetArgs(convertToPosixArgs(os.Args, rootCmd))
if err := rootCmd.Execute(); err != nil {
log.Fatalf("Error occurred during command execution: %v", err)
}
}