| // Copyright (c) The OpenTofu Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| // Copyright (c) 2023 HashiCorp, Inc. | |
| // SPDX-License-Identifier: MPL-2.0 | |
| package workdir | |
| import ( | |
| "fmt" | |
| "os" | |
| "strings" | |
| ) | |
| // runChdir gets the args that the program was executed with, extracts the | |
| // -chdir flag from it and runs [os.Chdir] with the associated flag value, if any provided. | |
| // | |
| // It returns back the list of arguments containing without the -chdir flag and value to be used | |
| // later by the invoked command. | |
| // TODO meta-refactor: this is a temporary solution until we will switch the CLI library that | |
| // will be able to handle root-only flags in a similar fashion with what we are doing here manually. | |
| func runChdir(args []string) ([]string, error) { | |
| // The arguments can begin with a -chdir option to ask OpenTofu to switch | |
| // to a different working directory for the rest of its work. If that | |
| // option is present then extractChdirOption returns a trimmed args with that option removed. | |
| overrideWd, args, err := extractChdirOption(args) | |
| if err != nil { | |
| return nil, fmt.Errorf("Invalid -chdir option: %s", err) | |
| } | |
| err = runChdirDirect(overrideWd) | |
| return args, err | |
| } | |
| func runChdirDirect(overrideWd string) error { | |
| if overrideWd != "" { | |
| err := os.Chdir(overrideWd) | |
| if err != nil { | |
| return fmt.Errorf("Error handling -chdir option: %s", err) | |
| } | |
| } | |
| return nil | |
| } | |
| // extractChdirOption extracts the -chdir flag together with the associated flag value, from the given args, | |
| // if any provided. | |
| // | |
| // It returns back the list of arguments without the -chdir flag and value to be used later by the invoked command. | |
| // This returns also an error in case the -chdir flag is given with the wrong values. | |
| // | |
| // TODO meta-refactor: remove this once the current CLI library is replaced. | |
| func extractChdirOption(args []string) (string, []string, error) { | |
| if len(args) == 0 { | |
| return "", args, nil | |
| } | |
| const argName = "-chdir" | |
| const argPrefix = argName + "=" | |
| var argValue string | |
| var argPos int | |
| for i, arg := range args { | |
| if !strings.HasPrefix(arg, "-") { | |
| // Because the chdir option is a subcommand-agnostic one, we require | |
| // it to appear before any subcommand argument, so if we find a | |
| // non-option before we find -chdir then we are finished. | |
| break | |
| } | |
| if arg == argName || arg == argPrefix { | |
| return "", args, fmt.Errorf("must include an equals sign followed by a directory path, like -chdir=example") | |
| } | |
| if strings.HasPrefix(arg, argPrefix) { | |
| argPos = i | |
| argValue = arg[len(argPrefix):] | |
| } | |
| } | |
| // When we fall out here, we'll have populated argValue with a non-empty | |
| // string if the -chdir=... option was present and valid, or left it | |
| // empty if it wasn't present. | |
| if argValue == "" { | |
| return "", args, nil | |
| } | |
| // If we did find the option then we'll need to produce a new args that | |
| // doesn't include it anymore. | |
| if argPos == 0 { | |
| // Easy case: we can just slice off the front | |
| return argValue, args[1:], nil | |
| } | |
| // Otherwise we need to construct a new array and copy to it. | |
| newArgs := make([]string, len(args)-1) | |
| copy(newArgs, args[:argPos]) | |
| copy(newArgs[argPos:], args[argPos+1:]) | |
| return argValue, newArgs, nil | |
| } |