diff --git a/README.md b/README.md index fdcaf36..81816c4 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ - [ ] More testing for critical parts of the tool - [x] Sophisticated parsing of config json keys with glob expansion - [x] Add support of adding new config files with sb -- [ ] Helper command to show all configs file for a binary and their content and which will be applied +- [x] Helper command to show all configs file for a binary and their content and which will be applied - [ ] Add support for removing config files with sb - [ ] Possibility to add overall config json file to apply to all commands - [ ] Add TLDR; for README diff --git a/cmd/sb/sb.go b/cmd/sb/sb.go index 79131df..0b13eae 100644 --- a/cmd/sb/sb.go +++ b/cmd/sb/sb.go @@ -7,7 +7,6 @@ import ( "os/user" "sb/internal/log" "sb/internal/osHelper" - "sb/internal/parse" "sb/internal/sandbox" "sb/internal/types" "sb/internal/util" @@ -25,9 +24,10 @@ func main() { // set config parameter setConfigParams(&context, input) + // parse config files if context.Config.CliConfig == nil { - context.Config.SbConfig = parse.ConfigFileParsing(&context) + context.Config.SbConfig = util.ConfigFileParsing(&context) } else { log.LogDebug("Using cli options") context.Config.SbConfig = context.Config.CliConfig @@ -58,15 +58,43 @@ func parseEnvs() { } } -func setConfigParams(context *types.Context, args []string) { - cliOptions, cliConfig, commands := parse.OptionsParsing(&context.Paths, args[1:]) - if types.CliOptions.EditEnabled { - util.EditFile(commands, context.Paths) +func checkCliOptions(context *types.Context, commands []string) { + for _, currentOption := range context.Config.CliOptions { + if currentOption == "--debug" || currentOption == "-d" { + types.CliOptions.DebugEnabled = true + } else if currentOption == "--dry-run" || currentOption == "-dr" { + types.CliOptions.DryRunEnabled = true + } else if currentOption == "--create-exe" || currentOption == "-ce" { + types.CliOptions.CreateExeEnabled = true + } else if currentOption == "--help" || currentOption == "-h" { + util.PrintHelp() + } else if currentOption == "--version" || currentOption == "-v" { + util.ShowVersion() + } else if currentOption == "--init" || currentOption == "-i" { + util.Init(&context.Paths) + } else if currentOption == "--edit" || currentOption == "-e" { + util.EditFile(commands, context.Paths) + } else if currentOption == "--show" || currentOption == "-s" { + fmt.Println(commands) + if len(commands) != 1 { + log.LogErr(` +You need to specify which config files you want to see +E.g. sb -s npm +`) + } + util.ShowConfig(context, commands[0]) + } } +} + +func setConfigParams(context *types.Context, args []string) { + cliOptions, cliConfig, commands := util.OptionsParsing(&context.Paths, args[1:]) context.Config.CliConfig = cliConfig if len(cliOptions) != 0 { context.Config.CliOptions = cliOptions } + // Check which options are enabled from the user + checkCliOptions(context, commands) context.Paths.BinaryPath = getPathToExecutable(commands[0]) context.Config.BinaryName = commands[0] context.Config.Commands = commands[1:] @@ -85,7 +113,6 @@ func configAllPath(context *types.Context) { context.Paths.RootConfigPath = context.Paths.HomePath + types.ConfigRepo context.Paths.WorkingDir = getWorkingDir() context.Paths.BinPath = "/usr/bin" - context.Paths.LocalConfigPath = context.Paths.WorkingDir + types.LocalConfigPath sbBinPath, err := os.Executable() if err != nil { log.LogErr(err) diff --git a/internal/types/cliOptions.go b/internal/types/cliOptions.go index 41b36a3..2171824 100644 --- a/internal/types/cliOptions.go +++ b/internal/types/cliOptions.go @@ -32,4 +32,5 @@ var ValidCliOptions = map[string]string{ "-i": "-i", "--edit": "--edit", "-e": "-e", + "-s": "--show", } diff --git a/internal/sandbox/configs/default.sb b/internal/util/configs/default.sb similarity index 100% rename from internal/sandbox/configs/default.sb rename to internal/util/configs/default.sb diff --git a/internal/sandbox/configs/node.json b/internal/util/configs/node.json similarity index 100% rename from internal/sandbox/configs/node.json rename to internal/util/configs/node.json diff --git a/internal/sandbox/configs/npm.json b/internal/util/configs/npm.json similarity index 100% rename from internal/sandbox/configs/npm.json rename to internal/util/configs/npm.json diff --git a/internal/util/edit.go b/internal/util/edit.go index 83f395f..781d7b1 100644 --- a/internal/util/edit.go +++ b/internal/util/edit.go @@ -11,6 +11,7 @@ import ( ) func EditFile(commands []string, paths types.Paths) { + fmt.Println(commands) if len(commands) != 2 { showError() } @@ -26,9 +27,21 @@ func EditFile(commands []string, paths types.Paths) { defaultEditor = string(output[:len(output)-1]) } if commands[0] == "root" { - path = getPath(commands[1], paths.RootConfigPath, path) + path = getPath(commands[1], paths.RootConfigPath) } else if commands[0] == "local" { - path = getPath(commands[1], paths.LocalConfigPath, path) + allPath := GetSubdirectories(paths.WorkingDir, paths.HomePath) + var localConfigExists bool + for _, localPath := range allPath { + localConfigPath := localPath + types.LocalConfigPath + localConfigExists, _ = DoesPathExist(localConfigPath) + if localConfigExists { + path = getPath(commands[1], localConfigPath) + break + } + } + if !localConfigExists { + log.LogErr("No local config directory found") + } } else { showError() } @@ -37,17 +50,17 @@ func EditFile(commands []string, paths types.Paths) { os.Exit(0) } -func getPath(binaryName string, configPath, path string) string { - path = filepath.Join(configPath, binaryName+".json") +func getPath(binaryName string, configPath string) string { + path := filepath.Join(configPath, binaryName+".json") if exists, _ := DoesPathExist(path); !exists { var answer string log.LogInfoSl(fmt.Sprintf("File does not exist, do you want to create it at path %v? (y)es/(n)o ", path)) if _, scanErr := fmt.Scanln(&answer); scanErr != nil { showErrorIfError(scanErr) - } else { - if configPathExists, err := DoesPathExist(configPath); !configPathExists { - showErrorIfError(err) - } + } + if answer != "y" { + log.LogInfoLn("No file is created, exiting sb") + os.Exit(0) } } return path @@ -60,10 +73,10 @@ func showErrorIfError(error error) { } func showError() { - log.LogErr(fmt.Printf(` + log.LogErr(` You must provide two values to edit a configuration file Valid options are: sb -e local sb -e root -`)) +`) } diff --git a/internal/parse/expandPaths_test.go b/internal/util/expandPaths_test.go similarity index 99% rename from internal/parse/expandPaths_test.go rename to internal/util/expandPaths_test.go index f00d100..fae282d 100644 --- a/internal/parse/expandPaths_test.go +++ b/internal/util/expandPaths_test.go @@ -1,4 +1,4 @@ -package parse +package util import ( "sb/internal/types" diff --git a/internal/util/help.go b/internal/util/help.go index c982bd4..c5ce81e 100644 --- a/internal/util/help.go +++ b/internal/util/help.go @@ -14,6 +14,7 @@ func PrintHelp() { "--dry-run -dr Do not execute the sandbox with the wanted binary will set debug and print to true so that you have all the information what sb would do\n" + "--create-exe -ce Creates executable shim for binary, so that you can use the sandboxed binary within other programs which would natively use the normal binary\n" + "--help -h Print this help section\n" + - "--version -v Show which version of sb is installed\n") + "--version -v Show which version of sb is installed\n" + + "--show -s Show location of all config files for this binary and the content of the file that would be applied\n") os.Exit(0) } diff --git a/internal/sandbox/init.go b/internal/util/init.go similarity index 98% rename from internal/sandbox/init.go rename to internal/util/init.go index 900b213..8e7d5d0 100644 --- a/internal/sandbox/init.go +++ b/internal/util/init.go @@ -1,4 +1,4 @@ -package sandbox +package util import ( "embed" @@ -9,7 +9,6 @@ import ( "path/filepath" "sb/internal/log" "sb/internal/types" - "sb/internal/util" ) func Init(paths *types.Paths) { @@ -96,7 +95,7 @@ var configDir embed.FS func createSbConfig(sbConfigPath string) { log.LogInfoLn("Creating .sb-config...") - if val, _ := util.DoesPathExist(sbConfigPath); val { + if val, _ := DoesPathExist(sbConfigPath); val { log.LogWarn(fmt.Sprintf("There is already an existing config directory at %v \nPlease create a backup or remove it to have the default configuration", sbConfigPath)) } else { log.LogInfoLn(fmt.Sprintf("Creating directory at destination %v", sbConfigPath)) diff --git a/internal/parse/parseCliOptions.go b/internal/util/parseCliOptions.go similarity index 80% rename from internal/parse/parseCliOptions.go rename to internal/util/parseCliOptions.go index 00ce999..1326489 100644 --- a/internal/parse/parseCliOptions.go +++ b/internal/util/parseCliOptions.go @@ -1,35 +1,14 @@ -package parse +package util import ( "errors" "path/filepath" "regexp" "sb/internal/log" - "sb/internal/sandbox" "sb/internal/types" - "sb/internal/util" "strings" ) -func parseCliOptions(paths *types.Paths, option string) { - currentOption := types.ValidCliOptions[option] - if currentOption == "--debug" || currentOption == "-d" { - types.CliOptions.DebugEnabled = true - } else if currentOption == "--dry-run" || currentOption == "-dr" { - types.CliOptions.DryRunEnabled = true - } else if currentOption == "--create-exe" || currentOption == "-ce" { - types.CliOptions.CreateExeEnabled = true - } else if currentOption == "--help" || currentOption == "-h" { - util.PrintHelp() - } else if currentOption == "--version" || currentOption == "-v" { - util.ShowVersion() - } else if currentOption == "--init" || currentOption == "-i" { - sandbox.Init(paths) - } else if currentOption == "--edit" || currentOption == "-e" { - types.CliOptions.EditEnabled = true - } -} - func OptionsParsing(paths *types.Paths, args []string) ([]string, *types.SbConfig, []string) { var options []string var cliConfigSb types.SbConfig @@ -40,7 +19,6 @@ func OptionsParsing(paths *types.Paths, args []string) ([]string, *types.SbConfi split, splitValue := parseCliConfigParam(value) if _, exist := types.ValidCliOptions[split]; exist { options = append(options, value) - parseCliOptions(paths, value) } else if _, configExists := types.AllowedConfigKeys[split]; configExists && len(splitValue) > 0 { cliConfig = addToCliConfig(paths, cliConfig, cliConfigSb, splitValue, split) } else { @@ -51,13 +29,14 @@ func OptionsParsing(paths *types.Paths, args []string) ([]string, *types.SbConfi break } } - if len(options) == len(args) { - log.LogErr("Please specify the program that you want to sandbox") + var commands []string + if len(args) != len(options) { + commands = args[optionsUntilIndex:] } if cliConfig != nil { - return options, cliConfig, args[optionsUntilIndex:] + return options, cliConfig, commands } else { - return options, nil, args[optionsUntilIndex:] + return options, nil, commands } } diff --git a/internal/parse/parseConfig.go b/internal/util/parseConfig.go similarity index 79% rename from internal/parse/parseConfig.go rename to internal/util/parseConfig.go index 6a8125f..b07da62 100644 --- a/internal/parse/parseConfig.go +++ b/internal/util/parseConfig.go @@ -1,16 +1,15 @@ -package parse +package util import ( "fmt" "sb/internal/log" "sb/internal/types" - "sb/internal/util" "slices" "strings" ) func doesRootConfigDirExist(path string) bool { - isRootConfigExisting, err := util.DoesPathExist(path) + isRootConfigExisting, err := DoesPathExist(path) if !isRootConfigExisting { log.LogWarn("Root config directory does not exist", err) // TODO: this could be on first run we might want to make sure it exists at this point @@ -20,7 +19,7 @@ func doesRootConfigDirExist(path string) bool { return true } -func getSubdirectories(path string, homePath string) []string { +func GetSubdirectories(path string, homePath string) []string { var allPaths []string currentPath := "" paths := strings.Split(path, "/") @@ -35,49 +34,71 @@ func getSubdirectories(path string, homePath string) []string { return allPaths } -// ConfigFileParsing Here we only parse the config files, cli configs have already been parsed -func ConfigFileParsing(context *types.Context) *types.SbConfig { - globalConfig := &types.SbConfig{} - allLocalPath := getSubdirectories(context.Paths.WorkingDir, context.Paths.HomePath) +func LocalConfigPath(paths *types.Paths, binaryName string) (string, bool) { + allLocalPath := GetSubdirectories(paths.WorkingDir, paths.HomePath) var localConfigExists bool var localConfigPath string for _, localPath := range allLocalPath { - localConfigPath = localPath + types.LocalConfigPath + "/" + context.Config.BinaryName + ".json" - localConfigExists, _ = util.DoesPathExist(localConfigPath) + localConfigPath = localPath + types.LocalConfigPath + "/" + binaryName + ".json" + localConfigExists, _ = DoesPathExist(localConfigPath) if localConfigExists { break } } + return localConfigPath, localConfigExists +} + +// ConfigFileParsing Here we only parse the config files, cli configs have already been parsed +func ConfigFileParsing(context *types.Context) *types.SbConfig { + globalConfig := &types.SbConfig{} + sbConfig, localConfigExists := extractLocalConfig(context) if localConfigExists { - localConfig := parseRootBinaryConfig(&context.Paths, localConfigPath, context.Config.Commands) - log.LogDebug("Using local config file") - return localConfig + return sbConfig + } + config, rootConfigExists := extractRootConfig(context, globalConfig) + if rootConfigExists { + return config + } + return nil +} + +func extractLocalConfig(context *types.Context) (*types.SbConfig, bool) { + localConfigPath, localConfigExists := LocalConfigPath(&context.Paths, context.Config.BinaryName) + if localConfigExists { + context.Paths.LocalConfigPath = localConfigPath + localConfig := parseJsonConfig(&context.Paths, localConfigPath, context.Config.Commands) + log.LogDebug("Using local config file at path ", localConfigPath) + return localConfig, true } else { log.LogDebug("No local config file found at: ", localConfigPath) log.LogDebug("Proceeding without local config") } + return nil, false +} + +func extractRootConfig(context *types.Context, globalConfig *types.SbConfig) (*types.SbConfig, bool) { if doesRootConfigDirExist(context.Paths.RootConfigPath) { binaryGlobalConfigPath := context.Paths.RootConfigPath + "/" + context.Config.BinaryName + ".json" - binaryPathExists, _ := util.DoesPathExist(binaryGlobalConfigPath) + binaryPathExists, _ := DoesPathExist(binaryGlobalConfigPath) if !binaryPathExists { log.LogWarn("No config for binary found. You might want to create a config file at: ", context.Paths.RootConfigPath) } else { - globalConfig = parseRootBinaryConfig(&context.Paths, binaryGlobalConfigPath, context.Config.Commands) + globalConfig = parseJsonConfig(&context.Paths, binaryGlobalConfigPath, context.Config.Commands) log.LogDebug("Using global config file") - return globalConfig + return globalConfig, true } } else { log.LogDebug("No root config file found at: ", context.Paths.RootConfigPath) log.LogDebug("Proceeding without global config") } - return nil + return nil, false } -func parseRootBinaryConfig(paths *types.Paths, path string, commands []string) *types.SbConfig { +func parseJsonConfig(paths *types.Paths, path string, commands []string) *types.SbConfig { mapping := buildCommandMap(commands) mapping["__root-config__"] = true var configs []*types.SbConfig - configJson := util.ParseJson(path) + configJson := ParseJson(path) for key, val := range configJson { var command string if strings.Contains(key, "*") { diff --git a/internal/parse/parseRootBinaryConfig_test.go b/internal/util/parseRootBinaryConfig_test.go similarity index 60% rename from internal/parse/parseRootBinaryConfig_test.go rename to internal/util/parseRootBinaryConfig_test.go index 3bea3f5..c3bae8b 100644 --- a/internal/parse/parseRootBinaryConfig_test.go +++ b/internal/util/parseRootBinaryConfig_test.go @@ -1,4 +1,4 @@ -package parse +package util import ( "sb/internal/types" @@ -10,18 +10,18 @@ import ( func TestRootConfig(t *testing.T) { var commands = []string{"run", "some"} paths := types.Paths{LocalConfigPath: "Users/test/sb", HomePath: "/Users/test", RootConfigPath: "Users/test", WorkingDir: "/Users/test/sb", BinPath: "/usr/bin", BinaryPath: "/usr/bin/ls"} - config := parseRootBinaryConfig(&paths, "./test.json", commands) + config := parseJsonConfig(&paths, "./test.json", commands) if len(config.Write) != 2 { - t.Errorf("parseRootBinaryConfig should have 2 entries for write but was %v", config.Write) + t.Errorf("parseJsonConfig should have 2 entries for write but was %v", config.Write) } if !slices.ContainsFunc(config.Write, func(s string) bool { return strings.Contains(s, "yes") }) { - t.Errorf("parseRootBinaryConfig config entry should have 'yes' in path but was %v", config.Write) + t.Errorf("parseJsonConfig config entry should have 'yes' in path but was %v", config.Write) } if !slices.ContainsFunc(config.Write, func(s string) bool { return strings.Contains(s, "okay") }) { - t.Errorf("parseRootBinaryConfig config entry should have 'okay' in path but was %v", config.Write) + t.Errorf("parseJsonConfig config entry should have 'okay' in path but was %v", config.Write) } } diff --git a/internal/util/show.go b/internal/util/show.go new file mode 100644 index 0000000..2f76c1f --- /dev/null +++ b/internal/util/show.go @@ -0,0 +1,31 @@ +package util + +import ( + "fmt" + "os" + "path/filepath" + "sb/internal/log" + "sb/internal/types" +) + +func ShowConfig(context *types.Context, binaryName string) { + localConfigPath, exists := LocalConfigPath(&context.Paths, binaryName) + if !exists { + log.LogInfoLn("Local Config not found, trying root config") + path := filepath.Join(context.Paths.RootConfigPath, binaryName+".json") + configExists, err := DoesPathExist(path) + if err != nil { + log.LogErr(err) + } + if configExists { + log.LogHighlight(fmt.Sprintf("Root config found at path %v", path)) + configJson := ParseJson(path) + log.LogInfoLn(log.PrettyJson(configJson)) + } + } else { + log.LogHighlight(fmt.Sprintf("Local config found at path %v", localConfigPath)) + configJson := ParseJson(localConfigPath) + log.LogInfoLn(log.PrettyJson(configJson)) + } + os.Exit(0) +} diff --git a/internal/parse/test.json b/internal/util/test.json similarity index 100% rename from internal/parse/test.json rename to internal/util/test.json