From ad3c9498cb1193621e1ed05289363475c2a42db2 Mon Sep 17 00:00:00 2001 From: i4k Date: Sun, 29 Sep 2024 22:32:25 +0100 Subject: [PATCH] refactor: disambiguate abs/rel paths with types. Signed-off-by: i4k --- cmd/terramate/cli/cli.go | 156 +++++++++--------- cmd/terramate/cli/cliconfig/cliconfig_test.go | 2 +- .../cli/cloud_sync_terraform_plan.go | 17 +- cmd/terramate/cli/github/event_test.go | 5 +- cmd/terramate/cli/project.go | 8 +- cmd/terramate/cli/run.go | 4 +- cmd/tgdeps/main.go | 16 +- config/config.go | 59 +++---- config/config_test.go | 11 +- config/stack.go | 31 ++-- e2etests/cloud/cloud_status_test.go | 11 +- e2etests/cloud/exp_trigger_cloud_test.go | 6 +- e2etests/cloud/run_cloud_deployment_test.go | 6 +- e2etests/cloud/run_cloud_drift_test.go | 6 +- e2etests/cloud/run_cloud_sync_preview_test.go | 2 +- .../cloud/run_script_cloud_deployment_test.go | 3 +- e2etests/cloud/run_script_cloud_drift_test.go | 2 +- .../cloud/run_script_cloud_preview_test.go | 2 +- e2etests/cmd/helper/main.go | 85 +++++----- e2etests/core/create_all_terraform_test.go | 9 +- e2etests/core/create_all_terragrunt_test.go | 5 +- e2etests/core/create_test.go | 14 +- e2etests/core/debug_show_generate_test.go | 9 +- e2etests/core/debug_show_globals_test.go | 6 +- e2etests/core/debug_show_metadata_test.go | 3 +- e2etests/core/exp_clone_test.go | 3 +- e2etests/core/exp_eval_test.go | 9 +- e2etests/core/exp_trigger_test.go | 16 +- e2etests/core/exp_vendor_test.go | 34 ++-- e2etests/core/fmt_test.go | 8 +- e2etests/core/general_test.go | 26 +-- e2etests/core/generate_test.go | 11 +- e2etests/core/list_git_test.go | 43 +++-- e2etests/core/run_env_test.go | 5 +- e2etests/core/run_parallel_test.go | 2 +- e2etests/core/run_test.go | 19 +-- e2etests/core/run_unix_test.go | 9 +- e2etests/core/safeguard_test.go | 56 +++---- e2etests/core/script_info_test.go | 3 +- e2etests/core/script_list_test.go | 3 +- e2etests/core/script_run_test.go | 3 +- e2etests/internal/runner/runner.go | 21 +-- errors/error.go | 2 +- fs/copy.go | 28 ++-- fs/copy_test.go | 10 +- fs/fs.go | 17 +- fs/fs_bench_test.go | 18 +- generate/generate.go | 143 ++++++++-------- generate/generate_hcl_test.go | 18 +- generate/generate_list_test.go | 6 +- generate/generate_root_file_test.go | 9 +- generate/generate_stack_file_test.go | 11 +- generate/generate_test.go | 29 ++-- generate/genhcl/genhcl.go | 3 +- generate/genhcl/genhcl_test.go | 3 +- generate/genhcl/partial_eval_test.go | 3 +- git/git.go | 39 ++--- git/git_test.go | 29 ++-- git/options.go | 4 +- git/url_test.go | 4 +- globals/globals_test.go | 5 +- hcl/ast/attribute.go | 5 +- hcl/ast/block.go | 7 +- hcl/ast/merged_block.go | 6 +- hcl/eval/eval_test.go | 5 +- hcl/eval/eval_unix_test.go | 5 +- hcl/eval/partial_eval_bench_test.go | 16 +- hcl/eval/partial_eval_test.go | 4 +- hcl/fmt/fmt.go | 35 ++-- hcl/fmt/fmt_test.go | 38 ++--- hcl/hcl.go | 124 +++++++------- hcl/hcl_cfg_run_test.go | 9 +- hcl/hcl_import_test.go | 25 +-- hcl/hcl_script_test.go | 3 +- hcl/hcl_stack_test.go | 3 +- hcl/hcl_test.go | 46 +++--- hcl/info/range.go | 22 ++- hcl/info/range_test.go | 21 +-- hcl/raw_config.go | 7 +- ls/commands.go | 3 +- ls/commands_test.go | 8 +- ls/ls.go | 58 ++++--- ls/ls_test.go | 19 ++- mapexpr/map.go | 2 +- modvendor/download/download.go | 81 ++++----- modvendor/download/download_test.go | 60 +++---- modvendor/download/event_test.go | 6 +- modvendor/download/manifest_test.go | 29 ++-- modvendor/manifest/manifest.go | 10 +- modvendor/modvendor.go | 7 +- modvendor/modvendor_common.go | 7 +- os/_test_mock.tf | 14 ++ os/hostpath.go | 47 ++++++ os/stack.tm.hcl | 6 + project/project.go | 57 ++++--- run/env_test.go | 3 +- run/order.go | 16 +- stack/clone.go | 59 +++---- stack/clone_test.go | 21 ++- stack/create.go | 9 +- stack/create_test.go | 8 +- stack/manager.go | 57 +++---- stack/manager_test.go | 94 +++++------ stack/trigger/trigger.go | 24 ++- stack/trigger/trigger_test.go | 7 +- stdlib/funcs.go | 28 ++-- stdlib/funcs_test.go | 3 +- test/config.go | 7 +- test/fs.go | 24 +-- test/git.go | 11 +- test/hcl.go | 7 +- test/hclutils/info/info.go | 14 +- test/hclutils/utils.go | 11 +- test/ls/editor.go | 20 +-- test/os.go | 83 +++++----- test/sandbox/git.go | 21 +-- test/sandbox/git_test.go | 2 +- test/sandbox/sandbox.go | 100 +++++------ test/stack.go | 3 +- tf/mod_source_parse.go | 3 - tf/terraform.go | 15 +- tf/terraform_test.go | 8 +- tg/funcs.go | 42 ++--- tg/tg_library_sanity_test.go | 18 +- tg/tg_module.go | 27 +-- 125 files changed, 1374 insertions(+), 1296 deletions(-) create mode 100644 os/_test_mock.tf create mode 100644 os/hostpath.go create mode 100644 os/stack.tm.hcl diff --git a/cmd/terramate/cli/cli.go b/cmd/terramate/cli/cli.go index f36b3666ea..a13f168c96 100644 --- a/cmd/terramate/cli/cli.go +++ b/cmd/terramate/cli/cli.go @@ -9,7 +9,7 @@ import ( stdfmt "fmt" "io" "net/http" - "os" + stdos "os" "path" "path/filepath" "strings" @@ -38,6 +38,7 @@ import ( "github.com/terramate-io/terramate/hcl/fmt" "github.com/terramate-io/terramate/hcl/info" "github.com/terramate-io/terramate/modvendor/download" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/printer" "github.com/terramate-io/terramate/safeguard" "github.com/terramate-io/terramate/tg" @@ -542,41 +543,43 @@ func newCLI(version string, args []string, stdin io.Reader, stdout, stderr io.Wr } if err != nil { printer.Stderr.Error(err) - os.Exit(1) + stdos.Exit(1) } output.MsgStdOut("authenticated successfully") return &cli{exit: true} } - wd, err := os.Getwd() + wdstr, err := stdos.Getwd() if err != nil { fatalWithDetailf(err, "getting workdir") } - logger = logger.With(). - Str("workingDir", wd). - Logger() - if parsedArgs.Chdir != "" { logger.Debug(). Str("dir", parsedArgs.Chdir). Msg("Changing working directory") - err = os.Chdir(parsedArgs.Chdir) + err = stdos.Chdir(parsedArgs.Chdir) if err != nil { fatalWithDetailf(err, "changing working dir to %s", parsedArgs.Chdir) } - wd, err = os.Getwd() + wdstr, err = stdos.Getwd() if err != nil { fatalf("getting workdir: %s", err) } } - wd, err = filepath.EvalSymlinks(wd) + wdstr, err = filepath.EvalSymlinks(wdstr) if err != nil { - fatalWithDetailf(err, "evaluating symlinks on working dir: %s", wd) + fatalWithDetailf(err, "evaluating symlinks on working dir: %s", wdstr) } + wd := os.NewHostPath(wdstr) + + logger = logger.With(). + Stringer("workingDir", wd). + Logger() + prj, foundRoot, err := lookupProject(wd) if err != nil { fatalWithDetailf(err, "unable to parse configuration") @@ -593,7 +596,7 @@ Alternatively you can create a Terramate config to make the current directory th Please see https://terramate.io/docs/cli/configuration/project-setup for details. `) - os.Exit(1) + stdos.Exit(1) } err = prj.setDefaults() @@ -610,7 +613,7 @@ Please see https://terramate.io/docs/cli/configuration/project-setup for details } uimode := HumanMode - if val := os.Getenv("CI"); envVarIsSet(val) { + if val := stdos.Getenv("CI"); envVarIsSet(val) { uimode = AutomationMode } @@ -643,7 +646,7 @@ func (c *cli) run() { logger := log.With(). Str("action", "run()"). Str("cmd", c.ctx.Command()). - Str("workingDir", c.wd()). + Stringer("workingDir", c.wd()). Logger() c.checkVersion() @@ -809,8 +812,8 @@ func (c *cli) vendorDownload() { ref := c.parsedArgs.Experimental.Vendor.Download.Reference logger := log.With(). - Str("workingDir", c.wd()). - Str("rootdir", c.rootdir()). + Stringer("workingDir", c.wd()). + Stringer("rootdir", c.rootdir()). Str("action", "cli.vendor()"). Str("source", source). Str("ref", ref). @@ -872,12 +875,8 @@ func (c *cli) handleVendorProgressEvents(eventsStream download.ProgressEventStre func (c *cli) vendorDir() prj.Path { if c.parsedArgs.Experimental.Vendor.Download.Dir != "" { - - dir := c.parsedArgs.Experimental.Vendor.Download.Dir - if !path.IsAbs(dir) { - dir = prj.PrjAbsPath(c.rootdir(), c.wd()).Join(dir).String() - } - return prj.NewPath(dir) + hostdir := os.HostPath(c.wd(), c.parsedArgs.Experimental.Vendor.Download.Dir) + return prj.PrjAbsPath(c.rootdir(), hostdir) } checkVendorDir := func(dir string) prj.Path { @@ -887,28 +886,22 @@ func (c *cli) vendorDir() prj.Path { return prj.NewPath(dir) } - dotTerramate := filepath.Join(c.rootdir(), ".terramate") - dotTerramateInfo, err := os.Stat(dotTerramate) - + dotTerramate := c.rootdir().Join(".terramate") + dotTerramateInfo, err := stdos.Stat(dotTerramate.String()) if err == nil && dotTerramateInfo.IsDir() { - - cfg, err := hcl.ParseDir(c.rootdir(), filepath.Join(c.rootdir(), ".terramate")) + cfg, err := hcl.ParseDir(c.rootdir(), dotTerramate) if err != nil { fatalWithDetailf(err, "parsing vendor dir configuration on .terramate") } if hasVendorDirConfig(cfg) { - return checkVendorDir(cfg.Vendor.Dir) } } - hclcfg := c.rootNode() if hasVendorDirConfig(hclcfg) { - return checkVendorDir(hclcfg.Vendor.Dir) } - return prj.NewPath(defaultVendorDir) } @@ -981,7 +974,7 @@ func (c *cli) triggerStackByFilter() { } } -func (c *cli) triggerStack(basePath string) { +func (c *cli) triggerStack(basePathStr string) { changeFlag := c.parsedArgs.Experimental.Trigger.Change ignoreFlag := c.parsedArgs.Experimental.Trigger.IgnoreChange @@ -1008,25 +1001,28 @@ func (c *cli) triggerStack(basePath string) { if reason == "" { reason = "Created using Terramate CLI without setting specific reason." } - if !path.IsAbs(basePath) { - basePath = filepath.Join(c.wd(), filepath.FromSlash(basePath)) + // TODO(i4k): ask about this!!! + if !filepath.IsAbs(basePathStr) { + basePathStr = c.wd().Join(basePathStr).String() } else { - basePath = filepath.Join(c.rootdir(), filepath.FromSlash(basePath)) + basePathStr = c.rootdir().Join(basePathStr).String() } - basePath = filepath.Clean(basePath) - _, err := os.Lstat(basePath) - if errors.Is(err, os.ErrNotExist) { + basePathStr = filepath.Clean(basePathStr) + _, err := stdos.Lstat(basePathStr) + if errors.Is(err, stdos.ErrNotExist) { fatalWithDetailf(err, "path not found") } - tmp, err := filepath.EvalSymlinks(basePath) + tmp, err := filepath.EvalSymlinks(basePathStr) if err != nil { fatalWithDetailf(err, "failed to evaluate stack path symlinks") } - if tmp != basePath { - fatal(stdfmt.Sprintf("symlinks are disallowed in the path: %s links to %s", basePath, tmp)) + if tmp != basePathStr { + fatal(stdfmt.Sprintf("symlinks are disallowed in the path: %s links to %s", basePathStr, tmp)) } - if !strings.HasPrefix(basePath, c.rootdir()) { - fatalf("path %s is outside project", basePath) + basePath := os.NewHostPath(basePathStr) + if !basePath.HasPrefix(c.rootdir().String()) { + // TODO(i4k): this possibly cannot ever happen!!! + fatalf("path %s is outside project", basePathStr) } prjBasePath := prj.PrjAbsPath(c.rootdir(), basePath) if c.parsedArgs.Experimental.Trigger.Status != "" && c.parsedArgs.Experimental.Trigger.Recursive { @@ -1066,8 +1062,8 @@ func (c *cli) cloneStack() { skipChildStacks := c.parsedArgs.Experimental.Clone.SkipChildStacks // Convert to absolute paths - absSrcdir := filepath.Join(c.wd(), srcdir) - absDestdir := filepath.Join(c.wd(), destdir) + absSrcdir := c.wd().Join(srcdir) + absDestdir := c.wd().Join(destdir) n, err := stack.Clone(c.cfg(), absDestdir, absSrcdir, skipChildStacks) if err != nil { @@ -1104,7 +1100,7 @@ func (c *cli) generate() { exitCode = 1 } - os.Exit(exitCode) + stdos.Exit(exitCode) } // gencodeWithVendor will generate code for the whole project providing automatic @@ -1124,7 +1120,7 @@ func (c *cli) gencodeWithVendor() (generate.Report, download.Report) { log.Debug().Msg("generating code") - cwd := prj.PrjAbsPath(c.cfg().HostDir(), c.wd()) + cwd := prj.PrjAbsPath(c.cfg().Path(), c.wd()) report := generate.Do(c.cfg(), cwd, c.vendorDir(), vendorRequestEvents) log.Debug().Msg("code generation finished, waiting for vendor requests to be handled") @@ -1439,14 +1435,14 @@ func (c *cli) initTerraform() { } if report.HasFailures() || vendorReport.HasFailures() { - os.Exit(1) + stdos.Exit(1) } c.output.MsgStdOutV(report.Full()) c.output.MsgStdOutV(vendorReport.String()) } -func (c *cli) initTerraformDir(baseDir string) error { +func (c *cli) initTerraformDir(baseDir os.Path) error { pdir := prj.PrjAbsPath(c.rootdir(), baseDir) var isStack bool tree, found := c.prj.root.Lookup(pdir) @@ -1454,14 +1450,14 @@ func (c *cli) initTerraformDir(baseDir string) error { isStack = tree.IsStack() } - dirs, err := os.ReadDir(baseDir) + dirs, err := stdos.ReadDir(baseDir.String()) if err != nil { fatalWithDetailf(err, "unable to read directory while listing directory entries") } errs := errors.L() for _, f := range dirs { - path := filepath.Join(baseDir, f.Name()) + path := baseDir.Join(f.Name()) if strings.HasPrefix(f.Name(), ".") { continue } @@ -1490,7 +1486,7 @@ func (c *cli) initTerraformDir(baseDir string) error { stackDir := baseDir stackID, err := uuid.NewRandom() - dirBasename := filepath.Base(stackDir) + dirBasename := stackDir.Base() if err != nil { fatalWithDetailf(err, "creating stack UUID") } @@ -1521,7 +1517,7 @@ func (c *cli) createStack() { return } - stackHostDir := filepath.Join(c.wd(), c.parsedArgs.Create.Path) + stackHostDir := c.wd().Join(c.parsedArgs.Create.Path) stackID := c.parsedArgs.Create.ID if stackID == "" { @@ -1535,7 +1531,7 @@ func (c *cli) createStack() { stackName := c.parsedArgs.Create.Name if stackName == "" { - stackName = filepath.Base(stackHostDir) + stackName = stackHostDir.Base() } stackDescription := c.parsedArgs.Create.Description @@ -1610,7 +1606,7 @@ func (c *cli) createStack() { } if report.HasFailures() || vendorReport.HasFailures() { - os.Exit(1) + stdos.Exit(1) } c.output.MsgStdOutV(report.Minimal()) @@ -1632,7 +1628,7 @@ func (c *cli) format() { } case 1: if c.parsedArgs.Fmt.Files[0] == "-" { - content, err := io.ReadAll(os.Stdin) + content, err := io.ReadAll(stdos.Stdin) if err != nil { fatalWithDetailf(err, "reading stdin") } @@ -1647,7 +1643,7 @@ func (c *cli) format() { if formatted != original { status = 1 } - os.Exit(status) + stdos.Exit(status) } stdfmt.Print(formatted) @@ -1656,21 +1652,25 @@ func (c *cli) format() { fallthrough default: + files := os.Paths{} + for _, f := range c.parsedArgs.Fmt.Files { + files = append(files, c.wd().Join(f)) + } var err error - results, err = fmt.FormatFiles(c.wd(), c.parsedArgs.Fmt.Files) + results, err = fmt.FormatFiles(c.wd(), files) if err != nil { fatalWithDetailf(err, "formatting files") } } for _, res := range results { - path := strings.TrimPrefix(res.Path(), c.wd()+string(filepath.Separator)) + path := strings.TrimPrefix(res.Path().String(), c.wd().String()+string(filepath.Separator)) c.output.MsgStdOut(path) } if len(results) > 0 { if c.parsedArgs.Fmt.Check { - os.Exit(1) + stdos.Exit(1) } } @@ -1684,7 +1684,7 @@ func (c *cli) format() { } if len(results) > 0 && c.parsedArgs.Fmt.DetailedExitCode { - os.Exit(2) + stdos.Exit(2) } } @@ -1826,7 +1826,7 @@ func (c *cli) generateGraph() { logger := log.With(). Str("action", "generateGraph()"). - Str("workingDir", c.wd()). + Stringer("workingDir", c.wd()). Logger() switch c.parsedArgs.Experimental.RunGraph.Label { @@ -1890,7 +1890,7 @@ func (c *cli) generateGraph() { out = c.stdout } else { - f, err := os.Create(outFile) + f, err := stdos.Create(outFile) if err != nil { fatalWithDetailf(err, "opening file %s", outFile) } @@ -1949,7 +1949,7 @@ func generateDot( func (c *cli) printRunOrder(friendlyFmt bool) { logger := log.With(). Str("action", "printRunOrder()"). - Str("workingDir", c.wd()). + Stringer("workingDir", c.wd()). Logger() stacks, err := c.computeSelectedStacks(false, cloudstack.AnyTarget, cloud.NoStatusFilters()) @@ -2258,7 +2258,7 @@ func (c *cli) setupEvalContext(st *config.Stack, overrideGlobals map[string]stri runtime["target"] = cty.StringVal(c.cloud.run.target) } - var tdir string + var tdir os.Path if st != nil { tdir = st.HostDir(c.cfg()) runtime.Merge(st.RuntimeValues(c.cfg())) @@ -2295,9 +2295,8 @@ func (c *cli) setupEvalContext(st *config.Stack, overrideGlobals map[string]stri globalPath, expr, info.NewRange(c.rootdir(), hhcl.Range{ - Filename: "", - Start: hhcl.InitialPos, - End: hhcl.InitialPos, + Start: hhcl.InitialPos, + End: hhcl.InitialPos, }), ) } @@ -2369,8 +2368,8 @@ func (c *cli) gitSafeguardRemoteEnabled() bool { return hasRemotes } -func (c *cli) wd() string { return c.prj.wd } -func (c *cli) rootdir() string { return c.prj.rootdir } +func (c *cli) wd() os.Path { return c.prj.wd } +func (c *cli) rootdir() os.Path { return c.prj.rootdir } func (c *cli) cfg() *config.Root { return &c.prj.root } func (c *cli) baseRef() string { return c.prj.baseRef } func (c *cli) stackManager() *stack.Manager { return c.prj.stackManager } @@ -2444,7 +2443,7 @@ func (c *cli) filterStacksByTags(entries []stack.Entry) []stack.Entry { func (c cli) checkVersion() { logger := log.With(). Str("action", "cli.checkVersion()"). - Str("root", c.rootdir()). + Stringer("root", c.rootdir()). Logger() rootcfg := c.rootNode() @@ -2554,10 +2553,10 @@ func (c *cli) setupFilterTags() { } } -func newGit(basedir string) (*git.Git, error) { +func newGit(basedir os.Path) (*git.Git, error) { g, err := git.WithConfig(git.Config{ WorkingDir: basedir, - Env: os.Environ(), + Env: stdos.Environ(), }) if err != nil { return nil, err @@ -2565,7 +2564,7 @@ func newGit(basedir string) (*git.Git, error) { return g, nil } -func lookupProject(wd string) (prj project, found bool, err error) { +func lookupProject(wd os.Path) (prj project, found bool, err error) { prj = project{ wd: wd, } @@ -2578,7 +2577,7 @@ func lookupProject(wd string) (prj project, found bool, err error) { if err == nil { gitabs := gitdir if !filepath.IsAbs(gitabs) { - gitabs = filepath.Join(wd, gitdir) + gitabs = wd.Join(gitdir).String() } rootdir, err := filepath.EvalSymlinks(gitabs) @@ -2586,16 +2585,17 @@ func lookupProject(wd string) (prj project, found bool, err error) { return project{}, false, errors.E(err, "failed evaluating symlinks of %q", gitabs) } - cfg, err := config.LoadRoot(rootdir) + root := os.NewHostPath(rootdir) + cfg, err := config.LoadRoot(root) if err != nil { return project{}, false, err } - gw = gw.With().WorkingDir(rootdir).Wrapper() + gw = gw.With().WorkingDir(root).Wrapper() prj.isRepo = true prj.root = *cfg - prj.rootdir = rootdir + prj.rootdir = root prj.git.wrapper = gw mgr := stack.NewGitAwareManager(&prj.root, gw) diff --git a/cmd/terramate/cli/cliconfig/cliconfig_test.go b/cmd/terramate/cli/cliconfig/cliconfig_test.go index b98078e923..e0d34eda44 100644 --- a/cmd/terramate/cli/cliconfig/cliconfig_test.go +++ b/cmd/terramate/cli/cliconfig/cliconfig_test.go @@ -148,7 +148,7 @@ func TestLoad(t *testing.T) { homeEntry := s.DirEntry("home") file := homeEntry.CreateFile(cliconfig.Filename, tc.cfg) - cfg, err := cliconfig.LoadFrom(file.HostPath()) + cfg, err := cliconfig.LoadFrom(file.Path()) errtest.Assert(t, err, tc.want.err) if err != nil { return diff --git a/cmd/terramate/cli/cloud_sync_terraform_plan.go b/cmd/terramate/cli/cloud_sync_terraform_plan.go index 55dfab73f1..9e3452b4e5 100644 --- a/cmd/terramate/cli/cloud_sync_terraform_plan.go +++ b/cmd/terramate/cli/cloud_sync_terraform_plan.go @@ -8,7 +8,7 @@ import ( "bytes" "context" "encoding/json" - "os" + stdos "os" "os/exec" "path/filepath" "time" @@ -17,6 +17,7 @@ import ( "github.com/terramate-io/terramate/cloud" "github.com/terramate-io/terramate/cmd/terramate/cli/clitest" "github.com/terramate-io/terramate/errors" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/tfjson" "github.com/terramate-io/tfjson/sanitize" ) @@ -45,11 +46,11 @@ func (c *cli) getTerraformChangeset(run stackCloudRun) (*cloud.ChangesetDetails, return nil, errors.E(clitest.ErrCloudInvalidTerraformPlanFilePath, "path must be relative to the running stack") } - absPlanFilePath := filepath.Join(run.Stack.HostDir(c.cfg()), planfile) + absPlanFilePath := run.Stack.HostDir(c.cfg()).Join(planfile) // Terragrunt writes the plan to a temporary directory, so we cannot check for its existence. if !run.Task.UseTerragrunt { - _, err := os.Lstat(absPlanFilePath) + _, err := stdos.Lstat(absPlanFilePath.String()) if err != nil { return nil, errors.E(err, "checking plan file") } @@ -137,7 +138,7 @@ func (c *cli) runTerraformShow(run stackCloudRun, flags ...string) (string, erro defer cancel() cmd := exec.CommandContext(ctx, cmdName, args...) - cmd.Dir = run.Stack.Dir.HostPath(c.rootdir()) + cmd.Dir = run.Stack.Dir.HostPath(c.rootdir()).String() cmd.Stdout = &stdout cmd.Stderr = &stderr cmd.Env = run.Env @@ -162,15 +163,15 @@ func (c *cli) runTerraformShow(run stackCloudRun, flags ...string) (string, erro return stdout.String(), nil } -func extractTFStateSerial(planfile string) (int64, bool) { +func extractTFStateSerial(planfile os.Path) (int64, bool) { logger := log.With(). Str("action", "extractTFStateSerial"). - Str("planfile", planfile). + Stringer("planfile", planfile). Logger() - planReader, err := zip.OpenReader(planfile) + planReader, err := zip.OpenReader(planfile.String()) if err != nil { - if b, err := os.ReadFile(planfile); err == nil { + if b, err := stdos.ReadFile(planfile.String()); err == nil { if bytes.HasPrefix(b, []byte("tfplan")) { logger.Debug().Msg("plan serial extraction failed: plan file was created with a pre 1.22 version of terraform") } else { diff --git a/cmd/terramate/cli/github/event_test.go b/cmd/terramate/cli/github/event_test.go index f308b88dbc..cd7703111e 100644 --- a/cmd/terramate/cli/github/event_test.go +++ b/cmd/terramate/cli/github/event_test.go @@ -5,7 +5,6 @@ package github_test import ( "os" - "path/filepath" "testing" "github.com/madlambda/spells/assert" @@ -29,7 +28,7 @@ func TestGetEventPR(t *testing.T) { draft bool } - nonExistentEventFile := filepath.Join(test.NonExistingDir(t), "event_pull_request.json") + nonExistentEventFile := test.NonExistingDir(t).Join("event_pull_request.json") type testcase struct { name string @@ -56,7 +55,7 @@ func TestGetEventPR(t *testing.T) { { name: "non existent path", env: map[string]string{ - "GITHUB_EVENT_PATH": nonExistentEventFile, + "GITHUB_EVENT_PATH": nonExistentEventFile.String(), }, want: want{ err: os.ErrNotExist, diff --git a/cmd/terramate/cli/project.go b/cmd/terramate/cli/project.go index ba5afe5e35..17b0177514 100644 --- a/cmd/terramate/cli/project.go +++ b/cmd/terramate/cli/project.go @@ -6,6 +6,8 @@ package cli import ( "fmt" + "github.com/terramate-io/terramate/os" + "github.com/rs/zerolog/log" "github.com/terramate-io/terramate/config" "github.com/terramate-io/terramate/errors" @@ -16,8 +18,8 @@ import ( ) type project struct { - rootdir string - wd string + rootdir os.Path + wd os.Path isRepo bool root config.Root baseRef string @@ -178,7 +180,7 @@ func (p project) defaultBranchRef() string { func (p *project) setDefaults() error { logger := log.With(). Str("action", "setDefaults()"). - Str("workingDir", p.wd). + Stringer("workingDir", p.wd). Logger() logger.Debug().Msg("Set defaults.") diff --git a/cmd/terramate/cli/run.go b/cmd/terramate/cli/run.go index 4544e23d8c..dfe3525edd 100644 --- a/cmd/terramate/cli/run.go +++ b/cmd/terramate/cli/run.go @@ -487,7 +487,7 @@ func (c *cli) runAll( cmd := exec.Command(backend.Command[0], backend.Command[1:]...) cmd.Stdout = &stdout cmd.Stderr = &stderr - cmd.Dir = otherStack.HostDir(c.cfg()) + cmd.Dir = otherStack.HostDir(c.cfg()).String() var inputVal cty.Value err := cmd.Run() if err != nil { @@ -579,7 +579,7 @@ func (c *cli) runAll( } cmd := exec.Command(cmdPath, task.Cmd[1:]...) - cmd.Dir = run.Stack.HostDir(c.cfg()) + cmd.Dir = run.Stack.HostDir(c.cfg()).String() cmd.Env = environ stdout := c.stdout diff --git a/cmd/tgdeps/main.go b/cmd/tgdeps/main.go index 5cafeb10c2..94d8c9bdbf 100644 --- a/cmd/tgdeps/main.go +++ b/cmd/tgdeps/main.go @@ -8,9 +8,10 @@ import ( "encoding/json" "flag" "fmt" - "os" + stdos "os" "github.com/rs/zerolog" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/tg" ) @@ -25,12 +26,15 @@ func main() { if *trace { zerolog.SetGlobalLevel(zerolog.TraceLevel) } - rootdir, err := os.Getwd() + rootdirStr, err := stdos.Getwd() abortOnErr(err) - var dir string + // assume current dir is project root dir. + rootdir := os.NewHostPath(rootdirStr) + + var dir os.Path if len(flag.Args()) == 2 { - dir = flag.Arg(1) + dir = rootdir.Join(flag.Arg(1)) } else { dir = rootdir } @@ -56,7 +60,7 @@ func main() { func abortOnErr(err error) { if err != nil { - fmt.Fprintf(os.Stderr, "error: %s\n", err) - os.Exit(1) + fmt.Fprintf(stdos.Stderr, "error: %s\n", err) + stdos.Exit(1) } } diff --git a/config/config.go b/config/config.go index 5f23381fab..1332fc16e8 100644 --- a/config/config.go +++ b/config/config.go @@ -7,7 +7,7 @@ import ( "bytes" "fmt" "io/fs" - "os" + stdos "os" "path" "path/filepath" "sort" @@ -25,6 +25,7 @@ import ( "github.com/terramate-io/terramate/hcl" "github.com/terramate-io/terramate/hcl/ast" "github.com/terramate-io/terramate/hcl/info" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/printer" "github.com/terramate-io/terramate/project" "github.com/zclconf/go-cty/cty" @@ -72,7 +73,7 @@ type Tree struct { root *Root stack *Stack - dir string + dir os.Path } // DirElem represents a node which is represented by a directory. @@ -89,7 +90,7 @@ type List[T DirElem] []T // the config in fromdir and all parent directories until / is reached. // If the configuration is found, it returns the whole configuration tree, // configpath != "" and found as true. -func TryLoadConfig(fromdir string) (tree *Root, configpath string, found bool, err error) { +func TryLoadConfig(fromdir os.Path) (tree *Root, configpath os.Path, found bool, err error) { for { ok, err := hcl.IsRootConfig(fromdir) if err != nil { @@ -130,7 +131,7 @@ func NewRoot(tree *Tree) *Root { } // LoadRoot loads the root configuration tree. -func LoadRoot(rootdir string) (*Root, error) { +func LoadRoot(rootdir os.Path) (*Root, error) { rootcfg, err := hcl.ParseDir(rootdir, rootdir) if err != nil { return nil, err @@ -148,7 +149,7 @@ func LoadRoot(rootdir string) (*Root, error) { func (root *Root) Tree() *Tree { return &root.tree } // HostDir returns the root directory. -func (root *Root) HostDir() string { return root.tree.RootDir() } +func (root *Root) Path() os.Path { return root.tree.Path() } // Lookup a node from the root using a filesystem query path. func (root *Root) Lookup(path project.Path) (*Tree, bool) { @@ -229,20 +230,20 @@ func (root *Root) LoadSubTree(cfgdir project.Path) error { parentNode = root.Tree() } - rootdir := root.HostDir() + rootdir := root.Path() relpath := strings.TrimPrefix(cfgdir.String(), parent.String()) relpath = strings.TrimPrefix(relpath, "/") components := strings.Split(relpath, "/") nextComponent := components[0] - subtreeDir := filepath.Join(rootdir, parent.String(), nextComponent) + subtreeDir := rootdir.Join(parent.String(), nextComponent) node, err := loadTree(root.Tree(), subtreeDir, nil) if err != nil { return errors.E(err, "failed to load config from %s", subtreeDir) } - if node.HostDir() == rootdir { + if node.Path() == rootdir { // root configuration reloaded *root = *NewRoot(node) } else { @@ -269,8 +270,8 @@ func (root *Root) Runtime() project.Runtime { func (root *Root) initRuntime() { rootfs := cty.ObjectVal(map[string]cty.Value{ - "absolute": cty.StringVal(root.HostDir()), - "basename": cty.StringVal(filepath.ToSlash(filepath.Base(root.HostDir()))), + "absolute": cty.StringVal(root.Path().String()), + "basename": cty.StringVal(filepath.ToSlash(filepath.Base(root.Path().String()))), }) rootpath := cty.ObjectVal(map[string]cty.Value{ "fs": rootfs, @@ -288,8 +289,8 @@ func (root *Root) initRuntime() { } } -// HostDir is the node absolute directory in the host. -func (tree *Tree) HostDir() string { +// Path is the node absolute directory in the host. +func (tree *Tree) Path() os.Path { return tree.dir } @@ -299,7 +300,7 @@ func (tree *Tree) Dir() project.Path { } // RootDir returns the tree root directory.. -func (tree *Tree) RootDir() string { +func (tree *Tree) RootDir() os.Path { if tree.Parent != nil { return tree.Parent.RootDir() } @@ -422,13 +423,13 @@ func (l List[T]) Len() int { return len(l) } func (l List[T]) Less(i, j int) bool { return l[i].Dir().String() < l[j].Dir().String() } func (l List[T]) Swap(i, j int) { l[i], l[j] = l[j], l[i] } -func loadTree(parentTree *Tree, cfgdir string, rootcfg *hcl.Config) (_ *Tree, err error) { +func loadTree(parentTree *Tree, cfgdir os.Path, rootcfg *hcl.Config) (_ *Tree, err error) { logger := log.With(). Str("action", "config.loadTree()"). - Str("dir", cfgdir). + Stringer("dir", cfgdir). Logger() - f, err := os.Open(cfgdir) + f, err := stdos.Open(cfgdir.String()) if err != nil { return nil, errors.E(err, "failed to open cfg directory") } @@ -469,7 +470,7 @@ func loadTree(parentTree *Tree, cfgdir string, rootcfg *hcl.Config) (_ *Tree, er tree.Node = cfg tree.Parent = parentTree - parentTree.Children[filepath.Base(cfgdir)] = tree + parentTree.Children[cfgdir.Base()] = tree parentTree = tree } @@ -485,7 +486,7 @@ func loadTree(parentTree *Tree, cfgdir string, rootcfg *hcl.Config) (_ *Tree, er continue } - dir := filepath.Join(cfgdir, fname) + dir := cfgdir.Join(fname) node, err := loadTree(parentTree, dir, rootcfg) if err != nil { return nil, errors.E(err, "loading from %s", dir) @@ -497,7 +498,7 @@ func loadTree(parentTree *Tree, cfgdir string, rootcfg *hcl.Config) (_ *Tree, er return parentTree, nil } -func processTmGenFiles(rootTree *Tree, cfg *hcl.Config, cfgdir string, dirEntries []fs.DirEntry) error { +func processTmGenFiles(rootTree *Tree, cfg *hcl.Config, cfgdir os.Path, dirEntries []fs.DirEntry) error { const tmgenSuffix = ".tmgen" tmgenEnabled := rootTree.hasExperiment("tmgen") @@ -509,7 +510,7 @@ func processTmGenFiles(rootTree *Tree, cfg *hcl.Config, cfgdir string, dirEntrie continue } - absFname := filepath.Join(cfgdir, fname) + absFname := cfgdir.Join(fname) if !tmgenEnabled { printer.Stderr.Warn( @@ -520,7 +521,7 @@ func processTmGenFiles(rootTree *Tree, cfg *hcl.Config, cfgdir string, dirEntrie continue } - content, err := os.ReadFile(absFname) + content, err := stdos.ReadFile(absFname.String()) if err != nil { return errors.E(err, "failed to read .tmgen file") } @@ -559,10 +560,10 @@ func processTmGenFiles(rootTree *Tree, cfg *hcl.Config, cfgdir string, dirEntrie implicitGenBlock := hcl.GenHCLBlock{ IsImplicitBlock: true, - Dir: project.PrjAbsPath(rootTree.HostDir(), cfgdir), + Dir: project.PrjAbsPath(rootTree.Path(), cfgdir), Inherit: inheritAttr, - Range: info.NewRange(rootTree.HostDir(), hhcl.Range{ - Filename: absFname, + Range: info.NewRange(rootTree.Path(), hhcl.Range{ + Filename: absFname.String(), Start: hhcl.InitialPos, End: hhcl.Pos{ Line: nLines, @@ -596,13 +597,13 @@ func (tree *Tree) NonEmptyGlobalsParent() *Tree { } // IsStack returns true if the given directory is a stack, false otherwise. -func IsStack(root *Root, dir string) bool { - node, ok := root.Lookup(project.PrjAbsPath(root.HostDir(), dir)) +func IsStack(root *Root, dir os.Path) bool { + node, ok := root.Lookup(project.PrjAbsPath(root.Path(), dir)) return ok && node.IsStack() } // NewTree creates a new tree node. -func NewTree(cfgdir string) *Tree { +func NewTree(cfgdir os.Path) *Tree { return &Tree{ dir: cfgdir, Children: make(map[string]*Tree), @@ -677,8 +678,8 @@ func Skip(name string) bool { return name[0] == '.' } -func parentDir(dir string) (string, bool) { - parent := filepath.Dir(dir) +func parentDir(dir os.Path) (os.Path, bool) { + parent := dir.Dir() return parent, parent != dir } diff --git a/config/config_test.go b/config/config_test.go index 96cc0244f8..937a2e11de 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -5,7 +5,6 @@ package config_test import ( "fmt" - "path/filepath" "testing" "github.com/madlambda/spells/assert" @@ -26,9 +25,9 @@ func TestIsStack(t *testing.T) { }) cfg := s.Config() - assert.IsTrue(t, !isStack(cfg, "/dir")) - assert.IsTrue(t, isStack(cfg, "/stack")) - assert.IsTrue(t, !isStack(cfg, "/stack/subdir")) + assert.IsTrue(t, !isStack(cfg, project.NewPath("/dir"))) + assert.IsTrue(t, isStack(cfg, project.NewPath("/stack"))) + assert.IsTrue(t, !isStack(cfg, project.NewPath("/stack/subdir"))) } func TestValidStackIDs(t *testing.T) { @@ -330,8 +329,8 @@ func TestConfigSkipdir(t *testing.T) { assert.IsTrue(t, !found) } -func isStack(root *config.Root, dir string) bool { - return config.IsStack(root, filepath.Join(root.HostDir(), dir)) +func isStack(root *config.Root, dir project.Path) bool { + return config.IsStack(root, root.Path().Join(dir.String())) } func init() { diff --git a/config/stack.go b/config/stack.go index 01a01ed33f..98b9e210fd 100644 --- a/config/stack.go +++ b/config/stack.go @@ -4,7 +4,7 @@ package config import ( - "os" + stdos "os" "path" "path/filepath" "regexp" @@ -13,6 +13,7 @@ import ( "github.com/terramate-io/terramate/config/tag" "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/hcl" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/zclconf/go-cty/cty" ) @@ -84,10 +85,10 @@ const ( ) // NewStackFromHCL creates a new stack from raw configuration cfg. -func NewStackFromHCL(root string, cfg hcl.Config) (*Stack, error) { +func NewStackFromHCL(root os.Path, cfg hcl.Config) (*Stack, error) { name := cfg.Stack.Name if name == "" { - name = filepath.Base(cfg.AbsDir()) + name = cfg.AbsDir().Base() } watchFiles, err := ValidateWatchPaths(root, cfg.AbsDir(), cfg.Stack.Watch) @@ -210,13 +211,13 @@ func (s *Stack) RelPath() string { return s.Dir.String()[1:] } // RelPathToRoot returns the relative path from the stack to root. func (s *Stack) RelPathToRoot(root *Root) string { // should never fail as abspath is constructed inside rootdir. - rel, _ := filepath.Rel(s.HostDir(root), root.HostDir()) + rel, _ := filepath.Rel(s.HostDir(root).String(), root.Path().String()) return filepath.ToSlash(rel) } // HostDir returns the file system absolute path of stack. -func (s *Stack) HostDir(root *Root) string { - return project.AbsPath(root.HostDir(), s.Dir.String()) +func (s *Stack) HostDir(root *Root) os.Path { + return s.Dir.HostPath(root.Path()) } // RuntimeValues returns the runtime "terramate" namespace for the stack. @@ -254,19 +255,19 @@ func (s *Stack) Sortable() *SortableStack { // ValidateWatchPaths validates if the provided watch paths points to regular files // inside the project repository. -func ValidateWatchPaths(rootdir string, stackpath string, paths []string) (project.Paths, error) { +func ValidateWatchPaths(rootdir os.Path, stackpath os.Path, paths []string) (project.Paths, error) { var projectPaths project.Paths for _, pathstr := range paths { - var abspath string + var abspath os.Path if path.IsAbs(pathstr) { - abspath = filepath.Join(rootdir, filepath.FromSlash(pathstr)) + abspath = rootdir.Join(filepath.FromSlash(pathstr)) } else { - abspath = filepath.Join(stackpath, filepath.FromSlash(pathstr)) + abspath = stackpath.Join(filepath.FromSlash(pathstr)) } - if !strings.HasPrefix(abspath, rootdir) { + if !abspath.HasPrefix(rootdir.String()) { return nil, errors.E("path %s is outside project root", pathstr) } - st, err := os.Stat(abspath) + st, err := stdos.Stat(abspath.String()) if err == nil { if st.IsDir() { return nil, errors.E("stack.watch must be a list of regular files "+ @@ -312,8 +313,8 @@ func LoadAllStacks(root *Root, cfg *Tree) (List[*SortableStack], error) { stacks = append(stacks, stack.Sortable()) if !*root.hasTerragruntStacks { - st, err := os.Lstat( - filepath.Join(stack.Dir.HostPath(root.HostDir()), "terragrunt.hcl"), + st, err := stdos.Lstat( + stack.Dir.HostPath(root.Path()).Join("terragrunt.hcl").String(), ) if err == nil && st.Mode().IsRegular() { *root.hasTerragruntStacks = true @@ -345,7 +346,7 @@ func LoadStack(root *Root, dir project.Path) (*Stack, error) { if !node.IsStack() { return nil, errors.E("config at %q is not a stack", dir) } - return NewStackFromHCL(root.HostDir(), node.Node) + return NewStackFromHCL(root.Path(), node.Node) } // TryLoadStack tries to load a single stack from dir. It sets found as true in case diff --git a/e2etests/cloud/cloud_status_test.go b/e2etests/cloud/cloud_status_test.go index c02bfb8aa2..85868ae5da 100644 --- a/e2etests/cloud/cloud_status_test.go +++ b/e2etests/cloud/cloud_status_test.go @@ -6,7 +6,6 @@ package cloud_test import ( "fmt" "os" - "path/filepath" "sort" "strconv" "testing" @@ -44,7 +43,7 @@ func TestCloudStatus(t *testing.T) { { name: "local repository is not permitted with --status=", layout: []string{"s:s1:id=s1"}, - repository: test.TempDir(t), + repository: test.TempDir(t).String(), flags: []string{`--status=unhealthy`}, want: RunExpected{ Status: 1, @@ -744,7 +743,7 @@ func TestCloudStatus(t *testing.T) { env = append(env, "TMC_API_PAGESIZE="+strconv.Itoa(tc.perPage)) } t.Run(tc.name+"/list", func(t *testing.T) { - cli := NewCLI(t, filepath.Join(s.RootDir(), tc.workingDir), env...) + cli := NewCLI(t, s.RootDir().Join(tc.workingDir), env...) args := []string{"list"} args = append(args, tc.flags...) result := cli.Run(args...) @@ -752,16 +751,16 @@ func TestCloudStatus(t *testing.T) { }) t.Run(tc.name+"/run", func(t *testing.T) { - cli := NewCLI(t, filepath.Join(s.RootDir(), tc.workingDir), env...) + cli := NewCLI(t, s.RootDir().Join(tc.workingDir), env...) args := []string{"run", "-X", "--quiet"} args = append(args, tc.flags...) - args = append(args, HelperPath, "stack-rel-path", s.RootDir()) + args = append(args, HelperPath, "stack-rel-path", s.RootDir().String()) result := cli.Run(args...) AssertRunResult(t, result, tc.want) }) t.Run(tc.name+"/script-run", func(t *testing.T) { - cli := NewCLI(t, filepath.Join(s.RootDir(), tc.workingDir), env...) + cli := NewCLI(t, s.RootDir().Join(tc.workingDir), env...) args := []string{"script", "run", "-X", "--quiet"} args = append(args, tc.flags...) args = append(args, "test") diff --git a/e2etests/cloud/exp_trigger_cloud_test.go b/e2etests/cloud/exp_trigger_cloud_test.go index d4609ebef1..fb84d6ce8b 100644 --- a/e2etests/cloud/exp_trigger_cloud_test.go +++ b/e2etests/cloud/exp_trigger_cloud_test.go @@ -96,7 +96,7 @@ func TestCloudTriggerUnhealthy(t *testing.T) { { name: "local repository is not permitted with --status=", layout: []string{"s:s1:id=s1"}, - repository: test.TempDir(t), + repository: test.TempDir(t).String(), flags: []string{`--status=unhealthy`}, want: want{ trigger: RunExpected{ @@ -678,7 +678,7 @@ func TestCloudTriggerUnhealthy(t *testing.T) { } env := RemoveEnv(os.Environ(), "CI") env = append(env, "TMC_API_URL=http://"+addr, "CI=") - cli := NewCLI(t, filepath.Join(s.RootDir(), tc.workingDir), env...) + cli := NewCLI(t, s.RootDir().Join(tc.workingDir), env...) args := []string{"experimental", "trigger"} args = append(args, tc.flags...) result := cli.Run(args...) @@ -686,7 +686,7 @@ func TestCloudTriggerUnhealthy(t *testing.T) { if tc.want.trigger.Status == 0 { s.Git().CommitAll("stacks triggered", true) - cli = NewCLI(t, filepath.Join(s.RootDir()), env...) + cli = NewCLI(t, s.RootDir(), env...) AssertRunResult(t, cli.ListChangedStacks(), tc.want.list) } }) diff --git a/e2etests/cloud/run_cloud_deployment_test.go b/e2etests/cloud/run_cloud_deployment_test.go index 141cd162be..99ca8c8e21 100644 --- a/e2etests/cloud/run_cloud_deployment_test.go +++ b/e2etests/cloud/run_cloud_deployment_test.go @@ -831,7 +831,7 @@ func TestCLIRunWithCloudSyncDeployment(t *testing.T) { if slices.Contains(tc.runflags, "--enable-sharing") { s.Generate() - cli := NewCLI(t, filepath.Join(s.RootDir(), filepath.FromSlash(tc.workingDir)), env...) + cli := NewCLI(t, s.RootDir().Join(tc.workingDir), env...) cli.PrependToPath(filepath.Dir(TerraformTestPath)) AssertRunResult(t, cli.Run("run", "-X", "--quiet", "--", "terraform", "init"), RunExpected{ Status: 0, @@ -841,7 +841,7 @@ func TestCLIRunWithCloudSyncDeployment(t *testing.T) { s.Git().CommitAll("all stacks committed") - cli := NewCLI(t, filepath.Join(s.RootDir(), filepath.FromSlash(tc.workingDir)), env...) + cli := NewCLI(t, s.RootDir().Join(tc.workingDir), env...) cli.PrependToPath(filepath.Dir(TerraformTestPath)) s.Git().SetRemoteURL("origin", testRemoteRepoURL) @@ -1001,7 +1001,7 @@ func TestRunGithubTokenDetection(t *testing.T) { oauth_token: abcd git_protocol: ssh `) - tm.AppendEnv = append(tm.AppendEnv, "GH_CONFIG_DIR="+ghConfigDir) + tm.AppendEnv = append(tm.AppendEnv, "GH_CONFIG_DIR="+ghConfigDir.String()) result := tm.Run("run", "--disable-check-git-remote", diff --git a/e2etests/cloud/run_cloud_drift_test.go b/e2etests/cloud/run_cloud_drift_test.go index 152c9e7d7c..e297c4a862 100644 --- a/e2etests/cloud/run_cloud_drift_test.go +++ b/e2etests/cloud/run_cloud_drift_test.go @@ -695,7 +695,7 @@ func TestCLIRunWithCloudSyncDriftStatus(t *testing.T) { env := RemoveEnv(s.Env, "CI") env = append(env, tc.env...) env = append(env, "TMC_API_URL=http://"+addr) - cli := NewCLI(t, filepath.Join(s.RootDir(), filepath.FromSlash(tc.workingDir)), env...) + cli := NewCLI(t, s.RootDir().Join(tc.workingDir), env...) s.Git().SetRemoteURL("origin", testRemoteRepoURL) runflags := []string{ "run", @@ -720,7 +720,7 @@ func TestCLIRunWithCloudSyncDriftStatus(t *testing.T) { assertRunDrifts(t, cloudData, addr, tc.want.drifts, minStartTime, maxEndTime) for _, wantDrift := range tc.want.drifts { - cli := NewCLI(t, filepath.Join(s.RootDir(), filepath.FromSlash(wantDrift.Stack.Path[1:])), env...) + cli := NewCLI(t, s.RootDir().Join(wantDrift.Stack.Path[1:]), env...) showArgs := []string{"cloud", "drift", "show"} if tc.target != "" { @@ -789,7 +789,7 @@ func TestSyncPlanSerial(t *testing.T) { s.BuildTree(layout) s.Git().CommitAll("all stacks committed") - cli := NewCLI(t, filepath.Join(s.RootDir(), filepath.FromSlash("")), env...) + cli := NewCLI(t, s.RootDir(), env...) cli.PrependToPath(filepath.Dir(TerraformTestPath)) s.Git().SetRemoteURL("origin", testRemoteRepoURL) runflags := []string{ diff --git a/e2etests/cloud/run_cloud_sync_preview_test.go b/e2etests/cloud/run_cloud_sync_preview_test.go index 16e42d903c..a9a1033c30 100644 --- a/e2etests/cloud/run_cloud_sync_preview_test.go +++ b/e2etests/cloud/run_cloud_sync_preview_test.go @@ -373,7 +373,7 @@ func TestCLIRunWithCloudSyncPreview(t *testing.T) { env = append(env, "GITHUB_EVENT_PATH="+tc.githubEventPath) env = append(env, "GITHUB_TOKEN=fake_token") env = append(env, tc.env...) - cli := NewCLI(t, filepath.Join(s.RootDir(), filepath.FromSlash(tc.workingDir)), env...) + cli := NewCLI(t, s.RootDir().Join(tc.workingDir), env...) cli.PrependToPath(filepath.Dir(TerraformTestPath)) s.Git().SetRemoteURL("origin", testPreviewRemoteRepoURL) diff --git a/e2etests/cloud/run_script_cloud_deployment_test.go b/e2etests/cloud/run_script_cloud_deployment_test.go index 45805d213d..2abcfed41e 100644 --- a/e2etests/cloud/run_script_cloud_deployment_test.go +++ b/e2etests/cloud/run_script_cloud_deployment_test.go @@ -6,7 +6,6 @@ package cloud_test import ( "fmt" "os" - "path/filepath" "testing" "github.com/madlambda/spells/assert" @@ -308,7 +307,7 @@ func TestCLIScriptRunWithCloudSyncDeployment(t *testing.T) { env := RemoveEnv(os.Environ(), "CI") env = append(env, "TMC_API_URL=http://"+addr) - cli := NewCLI(t, filepath.Join(s.RootDir(), filepath.FromSlash(tc.workingDir)), env...) + cli := NewCLI(t, s.RootDir().Join(tc.workingDir), env...) s.Git().SetRemoteURL("origin", testRemoteRepoURL) diff --git a/e2etests/cloud/run_script_cloud_drift_test.go b/e2etests/cloud/run_script_cloud_drift_test.go index 84e2724b2d..3bfd748eee 100644 --- a/e2etests/cloud/run_script_cloud_drift_test.go +++ b/e2etests/cloud/run_script_cloud_drift_test.go @@ -561,7 +561,7 @@ func TestScriptRunDriftStatus(t *testing.T) { env := RemoveEnv(s.Env, "CI") env = append(env, tc.env...) env = append(env, "TMC_API_URL=http://"+addr) - cli := NewCLI(t, filepath.Join(s.RootDir(), filepath.FromSlash(tc.workingDir)), env...) + cli := NewCLI(t, s.RootDir().Join(tc.workingDir), env...) s.Git().SetRemoteURL("origin", testRemoteRepoURL) runflags := []string{ "script", diff --git a/e2etests/cloud/run_script_cloud_preview_test.go b/e2etests/cloud/run_script_cloud_preview_test.go index 34bf2bfad5..c3867c6b9e 100644 --- a/e2etests/cloud/run_script_cloud_preview_test.go +++ b/e2etests/cloud/run_script_cloud_preview_test.go @@ -356,7 +356,7 @@ func TestScriptRunWithCloudSyncPreview(t *testing.T) { env = append(env, "GITHUB_EVENT_PATH="+tc.githubEventPath) env = append(env, "GITHUB_TOKEN=fake_token") env = append(env, tc.env...) - cli := NewCLI(t, filepath.Join(s.RootDir(), filepath.FromSlash(tc.workingDir)), env...) + cli := NewCLI(t, s.RootDir().Join(tc.workingDir), env...) cli.PrependToPath(filepath.Dir(TerraformTestPath)) s.Git().SetRemoteURL("origin", testPreviewRemoteRepoURL) diff --git a/e2etests/cmd/helper/main.go b/e2etests/cmd/helper/main.go index ff545d7148..d4050a57a6 100644 --- a/e2etests/cmd/helper/main.go +++ b/e2etests/cmd/helper/main.go @@ -10,7 +10,7 @@ import ( "encoding/json" "fmt" "log" - "os" + stdos "os" "os/signal" "path/filepath" "strconv" @@ -18,23 +18,24 @@ import ( "time" "github.com/terramate-io/terramate/git" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/tfjson" "github.com/terramate-io/tfjson/sanitize" ) func main() { - if len(os.Args) < 2 { - log.Fatalf("%s requires at least one subcommand argument", os.Args[0]) + if len(stdos.Args) < 2 { + log.Fatalf("%s requires at least one subcommand argument", stdos.Args[0]) } // note: unrecovered panic() aborts the program with exit code 2 and this // could be confused with a *detected drift* (see: run --sync-drift-status) // then avoid panics here and do proper os.Exit(1) in case of errors. - switch os.Args[1] { + switch stdos.Args[1] { case "echo": - args := os.Args[2:] + args := stdos.Args[2:] for i, arg := range args { fmt.Print(arg) if i+1 < len(args) { @@ -43,37 +44,37 @@ func main() { } fmt.Print("\n") case "true": - os.Exit(0) + stdos.Exit(0) case "false": - os.Exit(1) + stdos.Exit(1) case "exit": - exit(os.Args[2]) + exit(stdos.Args[2]) case "hang": hang() case "sleep": - sleep(os.Args[2]) + sleep(stdos.Args[2]) case "env": - env(os.Args[2], os.Args[3:]...) + env(os.NewHostPath(stdos.Args[2]), stdos.Args[3:]...) case "env-prefix": - envPrefix(os.Args[2], os.Args[3]) + envPrefix(os.NewHostPath(stdos.Args[2]), stdos.Args[3]) case "cat": - cat(os.Args[2]) + cat(stdos.Args[2]) case "rm": - rm(os.Args[2]) + rm(stdos.Args[2]) case "tempdir": tempDir() case "stack-abs-path": - stackAbsPath(os.Args[2]) + stackAbsPath(stdos.Args[2]) case "stack-rel-path": - stackRelPath(os.Args[2]) + stackRelPath(stdos.Args[2]) case "tf-plan-sanitize": - tfPlanSanitize(os.Args[2]) + tfPlanSanitize(stdos.Args[2]) case "fibonacci": fibonacci() case "git-normalization": - gitnorm(os.Args[2]) + gitnorm(stdos.Args[2]) default: - log.Fatalf("unknown command %s", os.Args[1]) + log.Fatalf("unknown command %s", stdos.Args[1]) } } @@ -82,7 +83,7 @@ func main() { // It will print "ready" when it starts to receive the signals. // It will print the name of the received signals, which may also be useful in testing. func hang() { - signals := make(chan os.Signal, 10) + signals := make(chan stdos.Signal, 10) signal.Notify(signals) fmt.Println("ready") @@ -104,17 +105,17 @@ func sleep(durationStr string) { func exit(exitCodeStr string) { code, err := strconv.Atoi(exitCodeStr) checkerr(err) - os.Exit(code) + stdos.Exit(code) } // env sends os.Environ() on stdout and exits. -func env(rootdir string, names ...string) { +func env(rootdir os.Path, names ...string) { if len(names) > 0 { - cwd, err := os.Getwd() + cwd, err := stdos.Getwd() checkerr(err) - dir := project.PrjAbsPath(rootdir, cwd) + dir := project.PrjAbsPath(rootdir, os.NewHostPath(cwd)) - for _, env := range os.Environ() { + for _, env := range stdos.Environ() { parts := strings.Split(env, "=") for _, n := range names { if parts[0] == n { @@ -124,16 +125,16 @@ func env(rootdir string, names ...string) { } return } - for _, env := range os.Environ() { + for _, env := range stdos.Environ() { fmt.Println(env) } } -func envPrefix(rootdir string, prefix string) { - cwd, err := os.Getwd() +func envPrefix(rootdir os.Path, prefix string) { + cwd, err := stdos.Getwd() checkerr(err) - dir := project.PrjAbsPath(rootdir, cwd) - for _, env := range os.Environ() { + dir := project.PrjAbsPath(rootdir, os.NewHostPath(cwd)) + for _, env := range stdos.Environ() { parts := strings.Split(env, "=") if strings.HasPrefix(parts[0], prefix) { fmt.Printf("%s: %s\n", dir, env) @@ -143,20 +144,20 @@ func envPrefix(rootdir string, prefix string) { // cat the file contents to stdout. func cat(fname string) { - bytes, err := os.ReadFile(fname) + bytes, err := stdos.ReadFile(fname) checkerr(err) fmt.Printf("%s", string(bytes)) } // rm remove the given path. func rm(fname string) { - err := os.RemoveAll(fname) + err := stdos.RemoveAll(fname) checkerr(err) } // tempdir creates a temporary directory. func tempDir() { - tmpdir, err := os.MkdirTemp("", "tm-tmpdir") + tmpdir, err := stdos.MkdirTemp("", "tm-tmpdir") checkerr(err) fmt.Print(tmpdir) } @@ -165,7 +166,7 @@ func tempDir() { // It may try to read values from ../fib.N-1/fib.txt and ../fib.N-2/fib.txt, which were previously // created by running this command in other dirs. func fibonacci() { - wd, err := os.Getwd() + wd, err := stdos.Getwd() checkerr(err) dirname := filepath.Base(wd) @@ -185,7 +186,7 @@ func fibonacci() { } else { v = 0 for _, i := range []int64{n - 1, n - 2} { - b, err := os.ReadFile(fmt.Sprintf("../fib.%v/fib.txt", i)) + b, err := stdos.ReadFile(fmt.Sprintf("../fib.%v/fib.txt", i)) checkerr(err) ni, err := strconv.ParseInt(string(b), 10, 64) checkerr(err) @@ -193,11 +194,11 @@ func fibonacci() { } } - checkerr(os.WriteFile("fib.txt", []byte(fmt.Sprintf("%v", v)), 0644)) + checkerr(stdos.WriteFile("fib.txt", []byte(fmt.Sprintf("%v", v)), 0644)) } func stackAbsPath(base string) { - cwd, err := os.Getwd() + cwd, err := stdos.Getwd() checkerr(err) rel, err := filepath.Rel(base, cwd) checkerr(err) @@ -205,7 +206,7 @@ func stackAbsPath(base string) { } func stackRelPath(base string) { - cwd, err := os.Getwd() + cwd, err := stdos.Getwd() checkerr(err) rel, err := filepath.Rel(base, cwd) checkerr(err) @@ -214,7 +215,7 @@ func stackRelPath(base string) { func tfPlanSanitize(fname string) { var oldPlan tfjson.Plan - oldPlanData, err := os.ReadFile(fname) + oldPlanData, err := stdos.ReadFile(fname) checkerr(err) err = json.Unmarshal(oldPlanData, &oldPlan) checkerr(err) @@ -228,8 +229,8 @@ func tfPlanSanitize(fname string) { func gitnorm(rawURL string) { repo, err := git.NormalizeGitURI(rawURL) if err != nil { - fmt.Fprintf(os.Stderr, "error: %v\n", err) - os.Exit(1) + fmt.Fprintf(stdos.Stderr, "error: %v\n", err) + stdos.Exit(1) } fmt.Printf("host: %s\n", repo.Host) fmt.Printf("owner: %s\n", repo.Owner) @@ -239,7 +240,7 @@ func gitnorm(rawURL string) { func checkerr(err error) { if err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - os.Exit(1) + fmt.Fprintf(stdos.Stderr, "%v\n", err) + stdos.Exit(1) } } diff --git a/e2etests/core/create_all_terraform_test.go b/e2etests/core/create_all_terraform_test.go index bfe3476e71..5c2e9af7fa 100644 --- a/e2etests/core/create_all_terraform_test.go +++ b/e2etests/core/create_all_terraform_test.go @@ -5,7 +5,6 @@ package core_test import ( "os" - "path/filepath" "testing" "github.com/madlambda/spells/assert" @@ -35,7 +34,7 @@ func TestCreateWithAllTerraformModuleAtRoot(t *testing.T) { Stdout: "Created stack /\n", }, ) - _, err := os.Lstat(filepath.Join(s.RootDir(), stack.DefaultFilename)) + _, err := os.Lstat(s.RootDir().Join(stack.DefaultFilename).String()) assert.NoError(t, err) } @@ -92,11 +91,11 @@ func TestCreateWithAllTerraformModuleDeepDownInTheTree(t *testing.T) { "/prod/stacks/B", "/prod/stacks/A/other-stack", } { - stackPath := filepath.Join(s.RootDir(), path) - _, err := os.Lstat(filepath.Join(stackPath, stack.DefaultFilename)) + stackPath := s.RootDir().Join(path) + _, err := os.Lstat(stackPath.Join(stack.DefaultFilename).String()) assert.NoError(t, err) - _, err = os.Lstat(filepath.Join(stackPath, "_generated.tf")) + _, err = os.Lstat(stackPath.Join("_generated.tf").String()) if generate { assert.NoError(t, err) } else { diff --git a/e2etests/core/create_all_terragrunt_test.go b/e2etests/core/create_all_terragrunt_test.go index 24058a32c0..2cca9dd86e 100644 --- a/e2etests/core/create_all_terragrunt_test.go +++ b/e2etests/core/create_all_terragrunt_test.go @@ -4,7 +4,6 @@ package core_test import ( - "path/filepath" "testing" . "github.com/terramate-io/terramate/e2etests/internal/runner" @@ -363,14 +362,14 @@ func TestCreateAllTerragrunt(t *testing.T) { t.Run(tc.name, func(t *testing.T) { s := sandbox.NoGit(t, true) s.BuildTree(tc.layout) - tm := NewCLI(t, filepath.Join(s.RootDir(), tc.wd)) + tm := NewCLI(t, s.RootDir().Join(tc.wd)) res := tm.Run("create", "--all-terragrunt") AssertRunResult(t, res, tc.want, ) if res.Status == 0 { - tm := NewCLI(t, filepath.Join(s.RootDir(), tc.wd)) + tm := NewCLI(t, s.RootDir().Join(tc.wd)) res := tm.Run("list", "--run-order") AssertRunResult(t, res, RunExpected{ Stdout: nljoin(tc.wantOrder...), diff --git a/e2etests/core/create_test.go b/e2etests/core/create_test.go index fdec59dc34..72da1d72e0 100644 --- a/e2etests/core/create_test.go +++ b/e2etests/core/create_test.go @@ -6,7 +6,6 @@ package core_test import ( "fmt" "path" - "path/filepath" "sort" "strings" "testing" @@ -14,6 +13,7 @@ import ( "github.com/google/uuid" "github.com/madlambda/spells/assert" . "github.com/terramate-io/terramate/e2etests/internal/runner" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/test" "github.com/terramate-io/terramate/test/sandbox" @@ -121,8 +121,8 @@ func TestCreateStack(t *testing.T) { ) createFile := func(s sandbox.S, path string) { - abspath := filepath.Join(s.RootDir(), path) - test.WriteFile(t, filepath.Dir(abspath), filepath.Base(abspath), "") + abspath := s.RootDir().Join(path) + test.WriteFile(t, abspath.Dir(), abspath.Base(), "") } testCreate := func(t *testing.T, flags ...string) { @@ -160,7 +160,7 @@ func TestCreateStack(t *testing.T) { }) s.ReloadConfig() - absStackPath := filepath.Join(s.RootDir(), filepath.FromSlash(stackPath)) + absStackPath := s.RootDir().Join(stackPath) got := s.LoadStack(project.PrjAbsPath(s.RootDir(), absStackPath)) stackBasePath := project.NewPath(path.Join("/", stackPath)) @@ -342,11 +342,11 @@ func testEnsureStackID(t *testing.T, wd string, layout []string) { s := sandbox.NoGit(t, true) s.BuildTree(layout) if wd == "" { - wd = s.RootDir() + wd = s.RootDir().String() } else { - wd = filepath.Join(s.RootDir(), filepath.FromSlash(wd)) + wd = s.RootDir().Join(wd).String() } - tm := NewCLI(t, wd) + tm := NewCLI(t, os.NewHostPath(wd)) AssertRunResult( t, tm.Run("create", "--ensure-stack-ids"), diff --git a/e2etests/core/debug_show_generate_test.go b/e2etests/core/debug_show_generate_test.go index ff707d8ceb..8497463c2f 100644 --- a/e2etests/core/debug_show_generate_test.go +++ b/e2etests/core/debug_show_generate_test.go @@ -5,7 +5,6 @@ package core_test import ( "fmt" - "path/filepath" "testing" . "github.com/terramate-io/terramate/e2etests/internal/runner" @@ -155,7 +154,7 @@ func TestGenerateDebug(t *testing.T) { root.CreateFile(config.path, config.body.String()) } - ts := NewCLI(t, filepath.Join(s.RootDir(), tc.wd)) + ts := NewCLI(t, s.RootDir().Join(tc.wd)) AssertRunResult(t, ts.Run("debug", "show", "generate-origins"), tc.want) }) } @@ -204,14 +203,14 @@ func TestGenerateDebugWithChanged(t *testing.T) { Stdout: want, }) - ts = NewCLI(t, filepath.Join(s.RootDir(), "stack-1")) + ts = NewCLI(t, s.RootDir().Join("stack-1")) AssertRunResult(t, ts.Run("debug", "show", "generate-origins", "--changed"), RunExpected{ Stdout: want, }) - ts = NewCLI(t, filepath.Join(s.RootDir(), "stack-2")) + ts = NewCLI(t, s.RootDir().Join("stack-2")) AssertRunResult(t, ts.Run("debug", "show", "generate-origins", "--changed"), RunExpected{}) - ts = NewCLI(t, filepath.Join(s.RootDir(), "no-stack")) + ts = NewCLI(t, s.RootDir().Join("no-stack")) AssertRunResult(t, ts.Run("debug", "show", "generate-origins", "--changed"), RunExpected{}) } diff --git a/e2etests/core/debug_show_globals_test.go b/e2etests/core/debug_show_globals_test.go index c5b58c75f7..df240b69ef 100644 --- a/e2etests/core/debug_show_globals_test.go +++ b/e2etests/core/debug_show_globals_test.go @@ -4,11 +4,9 @@ package core_test import ( - "path/filepath" "testing" . "github.com/terramate-io/terramate/e2etests/internal/runner" - "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/test" "github.com/terramate-io/terramate/test/hclwrite" . "github.com/terramate-io/terramate/test/hclwrite/hclutils" @@ -247,12 +245,12 @@ stack "/stacks/stack-name": s.BuildTree(tcase.layout) for _, globalBlock := range tcase.globals { - path := filepath.Join(s.RootDir(), globalBlock.path) + path := s.RootDir().Join(globalBlock.path) test.AppendFile(t, path, "globals.tm", globalBlock.add.String()) } - ts := NewCLI(t, project.AbsPath(s.RootDir(), tcase.wd)) + ts := NewCLI(t, s.RootDir().Join(tcase.wd)) AssertRunResult(t, ts.Run("debug", "show", "globals"), tcase.want) } }) diff --git a/e2etests/core/debug_show_metadata_test.go b/e2etests/core/debug_show_metadata_test.go index e5bd9cf606..9d8f000db2 100644 --- a/e2etests/core/debug_show_metadata_test.go +++ b/e2etests/core/debug_show_metadata_test.go @@ -7,7 +7,6 @@ import ( "testing" . "github.com/terramate-io/terramate/e2etests/internal/runner" - "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/test/sandbox" ) @@ -257,7 +256,7 @@ stack "/stack": s := sandbox.NoGit(t, true) s.BuildTree(tc.layout) - cli := NewCLI(t, project.AbsPath(s.RootDir(), tc.wd)) + cli := NewCLI(t, s.RootDir().Join(tc.wd)) AssertRunResult(t, cli.Run("debug", "show", "metadata"), tc.want) }) } diff --git a/e2etests/core/exp_clone_test.go b/e2etests/core/exp_clone_test.go index 416c8d0b30..0525b5abe9 100644 --- a/e2etests/core/exp_clone_test.go +++ b/e2etests/core/exp_clone_test.go @@ -5,7 +5,6 @@ package core_test import ( "fmt" - "path/filepath" "testing" "github.com/madlambda/spells/assert" @@ -65,7 +64,7 @@ generate_hcl "test2.hcl" { StdoutRegex: cloneSuccessMsg(1, srcStack, destStack), }) - destdir := filepath.Join(s.RootDir(), destStack) + destdir := s.RootDir().Join(destStack) cfg := test.ParseTerramateConfig(t, destdir) if cfg.Stack == nil { t.Fatalf("cloned stack has no stack block: %v", cfg) diff --git a/e2etests/core/exp_eval_test.go b/e2etests/core/exp_eval_test.go index 9b4d339d3b..a7d2bc8c10 100644 --- a/e2etests/core/exp_eval_test.go +++ b/e2etests/core/exp_eval_test.go @@ -5,7 +5,6 @@ package core_test import ( "fmt" - "path/filepath" "testing" . "github.com/terramate-io/terramate/e2etests/internal/runner" @@ -281,13 +280,13 @@ func TestExpEval(t *testing.T) { s := sandbox.NoGit(t, false) s.BuildTree(tc.layout) for _, globalBlock := range tc.globals { - path := filepath.Join(s.RootDir(), globalBlock.path) + path := s.RootDir().Join(globalBlock.path) test.AppendFile(t, path, "globals.tm", globalBlock.add.String()) } test.WriteRootConfig(t, s.RootDir()) - ts := NewCLI(t, filepath.Join(s.RootDir(), tc.wd)) + ts := NewCLI(t, s.RootDir().Join(tc.wd)) globalArgs := []string{} for globalName, globalExpr := range tc.overrideGlobals { globalArgs = append(globalArgs, "--global") @@ -435,11 +434,11 @@ func TestGetConfigValue(t *testing.T) { s.BuildTree(tc.layout) for _, globalBlock := range tc.globals { - path := filepath.Join(s.RootDir(), globalBlock.path) + path := s.RootDir().Join(globalBlock.path) test.AppendFile(t, path, "globals.tm", globalBlock.add.String()) } - ts := NewCLI(t, filepath.Join(s.RootDir(), tc.wd)) + ts := NewCLI(t, s.RootDir().Join(tc.wd)) globalArgs := []string{} for globalName, globalExpr := range tc.overrideGlobals { globalArgs = append(globalArgs, "--global") diff --git a/e2etests/core/exp_trigger_test.go b/e2etests/core/exp_trigger_test.go index 847823355e..cc07e8f2d9 100644 --- a/e2etests/core/exp_trigger_test.go +++ b/e2etests/core/exp_trigger_test.go @@ -28,7 +28,7 @@ func TestTriggerWorksWithRelativeStackPath(t *testing.T) { git.CheckoutNew("trigger-the-stack") // execute terramate from `dir/` directory. - cli := NewCLI(t, filepath.Join(s.RootDir(), "dir")) + cli := NewCLI(t, s.RootDir().Join("dir")) AssertRunResult(t, cli.TriggerStack(trigger.Changed, "stacks/stack"), RunExpected{ StdoutRegex: "Created change trigger", }) @@ -50,7 +50,7 @@ func TestTriggerWorksRecursivelyFromRelativeStackPath(t *testing.T) { git.Push("main") git.CheckoutNew("trigger-the-stack") - cli := NewCLI(t, filepath.Join(s.RootDir(), "dir")) + cli := NewCLI(t, s.RootDir().Join("dir")) AssertRunResult(t, cli.Trigger("--recursive", "--status=ok"), RunExpected{ Status: 1, StderrRegex: regexp.QuoteMeta("cloud filters such as --status are incompatible with --recursive flag"), @@ -89,7 +89,7 @@ func TestTriggerRecursivelyFromParentStackPath(t *testing.T) { git.Push("main") git.CheckoutNew("trigger-the-stack") - cli := NewCLI(t, filepath.Join(s.RootDir(), "dir")) + cli := NewCLI(t, s.RootDir().Join("dir")) AssertRunResult(t, cli.TriggerRecursively(trigger.Changed, "stacks/stack1"), RunExpected{ IgnoreStdout: true, }) @@ -125,7 +125,7 @@ func TestTriggerFailsWithSymlinksInStackPath(t *testing.T) { git.Push("main") git.CheckoutNew("trigger-the-stack") - cli := NewCLI(t, filepath.Join(s.RootDir(), "dir")) + cli := NewCLI(t, s.RootDir().Join("dir")) AssertRunResult(t, cli.TriggerStack(trigger.Changed, "link-to-stack"), RunExpected{ Status: 1, StderrRegex: "symlinks are disallowed", @@ -158,7 +158,7 @@ func TestTriggerMustNotTriggerStacksOutsideProject(t *testing.T) { git1.Push("main") git1.CheckoutNew("trigger-the-stack") - relpath, err := filepath.Rel(project1.RootDir(), project2.RootDir()) + relpath, err := filepath.Rel(project1.RootDir().String(), project2.RootDir().String()) assert.NoError(t, err) cli := NewCLI(t, project1.RootDir()) @@ -232,7 +232,7 @@ func TestTriggerIgnoresDeletedTriggerForChange(t *testing.T) { assertNoChanges() triggerDir := trigger.Dir(s.RootDir()) - test.RemoveAll(t, triggerDir) + test.RemoveAll(t, triggerDir.String()) git.CommitAll("removed trigger") assertNoChanges() @@ -276,7 +276,7 @@ func TestTriggerIgnoresDeletedTriggerForIgnore(t *testing.T) { assertNoChanges() triggerDir := trigger.Dir(s.RootDir()) - test.RemoveAll(t, triggerDir) + test.RemoveAll(t, triggerDir.String()) git.CommitAll("removed trigger") @@ -432,7 +432,7 @@ func TestRunWontDetectAsChangeDeletedChangedTrigger(t *testing.T) { git.CheckoutNew("delete-trigger") - test.RemoveAll(t, trigger.Dir(s.RootDir())) + test.RemoveAll(t, trigger.Dir(s.RootDir()).String()) git.CommitAll("removed trigger") AssertRunResult(t, cli.Run( diff --git a/e2etests/core/exp_vendor_test.go b/e2etests/core/exp_vendor_test.go index eb39bef42a..fc397ca04d 100644 --- a/e2etests/core/exp_vendor_test.go +++ b/e2etests/core/exp_vendor_test.go @@ -11,6 +11,7 @@ import ( "github.com/madlambda/spells/assert" . "github.com/terramate-io/terramate/e2etests/internal/runner" "github.com/terramate-io/terramate/modvendor" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/test" . "github.com/terramate-io/terramate/test/hclwrite/hclutils" @@ -32,12 +33,12 @@ func TestVendorModule(t *testing.T) { modsrc := test.ParseSource(t, gitSource+"?ref=main") // Check default config and then different configuration precedences - checkVendoredFiles := func(t *testing.T, rootdir string, res RunResult, vendordir project.Path) { + checkVendoredFiles := func(t *testing.T, rootdir os.Path, res RunResult, vendordir project.Path) { AssertRunResult(t, res, RunExpected{IgnoreStdout: true}) clonedir := modvendor.AbsVendorDir(rootdir, vendordir, modsrc) - got := test.ReadFile(t, clonedir, filename) + got := test.ReadFile(t, clonedir.String(), filename) assert.EqualStrings(t, content, string(got)) } @@ -217,12 +218,12 @@ func TestVendorModuleRecursiveDependencyIsPatched(t *testing.T) { moduleDir := modvendor.AbsVendorDir(tmrootDir.Path(), vendordir, targetmodsrc) depsDir := modvendor.AbsVendorDir(tmrootDir.Path(), vendordir, depmodsrc) - got := test.ReadFile(t, moduleDir, "main.tf") + got := test.ReadFile(t, moduleDir.String(), "main.tf") assert.EqualStrings(t, fmt.Sprintf(moduleFileTemplate, "../../dep/main"), string(got)) - got = test.ReadFile(t, depsDir, "main.tf") + got = test.ReadFile(t, depsDir.String(), "main.tf") assert.EqualStrings(t, "", string(got)) } @@ -293,25 +294,16 @@ func TestModVendorRecursiveMustPatchAlreadyVendoredModules(t *testing.T) { assert.NoError(t, err) vendorDir := project.NewPath("/modules") - modFileA := filepath.Join( - modvendor.AbsVendorDir(s.RootDir(), vendorDir, modsrcA), - filename, - ) - - modFileB := filepath.Join( - modvendor.AbsVendorDir(s.RootDir(), vendorDir, modsrcB), - filename, - ) + modFileA := modvendor.AbsVendorDir(s.RootDir(), vendorDir, modsrcA).Join(filename) + modFileB := modvendor.AbsVendorDir(s.RootDir(), vendorDir, modsrcB).Join(filename) - modFileC := filepath.Join( - modvendor.AbsVendorDir(s.RootDir(), vendorDir, modsrcC), - filename, - ) + modFileC := modvendor.AbsVendorDir(s.RootDir(), vendorDir, modsrcC).Join(filename) wantedFileContent := func(name string, modsrc, modsrcDep tf.Source) string { relPath, err := filepath.Rel( - modvendor.AbsVendorDir(s.RootDir(), vendorDir, modsrc), - modvendor.AbsVendorDir(s.RootDir(), vendorDir, modsrcDep)) + modvendor.AbsVendorDir(s.RootDir(), vendorDir, modsrc).String(), + modvendor.AbsVendorDir(s.RootDir(), vendorDir, modsrcDep).String(), + ) assert.NoError(t, err) return Module( Labels(name), @@ -324,8 +316,8 @@ func TestModVendorRecursiveMustPatchAlreadyVendoredModules(t *testing.T) { test.AssertFileContentEquals(t, modFileC, wantedFileContent("modC", modsrcC, modsrcZtest)) } -func newLocalSource(path string) string { - return "git::" + string(uri.File(path)) +func newLocalSource(path os.Path) string { + return "git::" + string(uri.File(path.String())) } func vendorHCLConfig(dir string) string { diff --git a/e2etests/core/fmt_test.go b/e2etests/core/fmt_test.go index 961332d489..793f2cc1eb 100644 --- a/e2etests/core/fmt_test.go +++ b/e2etests/core/fmt_test.go @@ -106,7 +106,7 @@ name = "name" t.Run("checking fails with unformatted files on subdirs", func(t *testing.T) { writeUnformattedFiles() - subdir := filepath.Join(s.RootDir(), "another-stacks") + subdir := s.RootDir().Join("another-stacks") cli := NewCLI(t, subdir) AssertRunResult(t, cli.Run("fmt", "--check"), RunExpected{ Status: 1, @@ -154,7 +154,7 @@ name = "name" t.Run("update unformatted files in subdirs", func(t *testing.T) { writeUnformattedFiles() - anotherStacks := filepath.Join(s.RootDir(), "another-stacks") + anotherStacks := s.RootDir().Join("another-stacks") cli := NewCLI(t, anotherStacks) AssertRunResult(t, cli.Run("fmt"), RunExpected{ Stdout: filesListOutput([]string{ @@ -173,7 +173,7 @@ name = "name" assertFileContents(t, "stacks/stack-1/globals.tm", unformattedHCL) assertFileContents(t, "stacks/stack-2/globals.tm", unformattedHCL) - stacks := filepath.Join(s.RootDir(), "stacks") + stacks := s.RootDir().Join("stacks") cli = NewCLI(t, stacks) AssertRunResult(t, cli.Run("fmt"), RunExpected{ Stdout: filesListOutput([]string{ @@ -394,7 +394,7 @@ name="name" files := tc.files if tc.absPaths { for i, f := range files { - files[i] = filepath.Join(s.RootDir(), f) + files[i] = s.RootDir().Join(f).String() } } args := []string{"fmt"} diff --git a/e2etests/core/general_test.go b/e2etests/core/general_test.go index 39c7fa0794..c52de265a4 100644 --- a/e2etests/core/general_test.go +++ b/e2etests/core/general_test.go @@ -5,10 +5,10 @@ package core_test import ( "fmt" - "path/filepath" "testing" . "github.com/terramate-io/terramate/e2etests/internal/runner" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/test" "github.com/terramate-io/terramate/test/sandbox" ) @@ -185,7 +185,7 @@ func TestListAndRunChangedStackInAbsolutePath(t *testing.T) { git.CommitAll("stack changed") wantList := stack.Path() + "\n" - AssertRunResult(t, cli.ListChangedStacks(), RunExpected{Stdout: wantList}) + AssertRunResult(t, cli.ListChangedStacks(), RunExpected{Stdout: wantList.String()}) wantRun := fmt.Sprintf( "Running on changed stacks:\n[%s] running %s %s %s\n%s\n", @@ -223,7 +223,7 @@ func TestDefaultBaseRefInOtherThanMain(t *testing.T) { git.CheckoutNew("change-the-stack") stackFile.Write("# changed") - git.Add(stack.Path()) + git.Add(stack.Path().String()) git.Commit("stack changed") want := RunExpected{ @@ -343,13 +343,13 @@ func TestFailsOnChangeDetectionIfRepoDoesntHaveOriginMain(t *testing.T) { assertFailsWithChanged("flag --changed requires a repository with at least two commits") path := test.WriteFile(t, git.BaseDir(), "file1.md", "# generated by terramate") - git.Add(path) + git.Add(path.String()) git.Commit("first commit") // need two commits for changed to make sense. assertFailsWithChanged("flag --changed requires a repository with at least two commits") path = test.WriteFile(t, git.BaseDir(), "file2.md", "# generated by terramate") - git.Add(path) + git.Add(path.String()) git.Commit("second commit") // have to set default remote explicitly, otherwise fallback to local @@ -454,7 +454,7 @@ func TestChangedWorksWithoutRemote(t *testing.T) { ) path := test.WriteFile(t, git.BaseDir(), "file1", "# generated by terramate") - git.Add(path) + git.Add(path.String()) git.Commit("first commit") AssertRunResult(t, @@ -466,7 +466,7 @@ func TestChangedWorksWithoutRemote(t *testing.T) { ) path = test.WriteFile(t, git.BaseDir(), "file2", "# generated by terramate") - git.Add(path) + git.Add(path.String()) git.Commit("second commit") assertSuccess(t) @@ -501,10 +501,10 @@ func TestRemoteSafeguardDefaults(t *testing.T) { cli := NewCLI(t, git.BaseDir()) path := test.WriteFile(t, git.BaseDir(), "file1", "# generated by terramate") - git.Add(path) + git.Add(path.String()) git.Commit("first commit") path = test.WriteFile(t, git.BaseDir(), "file2", "# generated by terramate") - git.Add(path) + git.Add(path.String()) git.Commit("second commit") AssertRun(t, cli.ListChangedStacks()) @@ -571,7 +571,7 @@ func TestE2ETerramateLogsWarningIfRootConfigIsNotAtProjectRoot(t *testing.T) { "s:stacks/stack", }) - stacksDir := filepath.Join(s.RootDir(), "stacks") + stacksDir := s.RootDir().Join("stacks") test.WriteRootConfig(t, stacksDir) tmcli := NewCLI(t, stacksDir) @@ -600,7 +600,7 @@ func TestBug515(t *testing.T) { } `) - assertListStacks := func(workdir, want string) { + assertListStacks := func(workdir os.Path, want string) { t.Helper() tmcli := NewCLI(t, workdir) @@ -610,8 +610,8 @@ func TestBug515(t *testing.T) { } assertListStacks(s.RootDir(), "stacks/stack\n") - assertListStacks(filepath.Join(s.RootDir(), "stacks"), "stack\n") - assertListStacks(filepath.Join(s.RootDir(), "stacks", "stack"), ".\n") + assertListStacks(s.RootDir().Join("stacks"), "stack\n") + assertListStacks(s.RootDir().Join("stacks", "stack"), ".\n") } func setupLocalMainBranchBehindOriginMain(git *sandbox.Git, changeFiles func()) { diff --git a/e2etests/core/generate_test.go b/e2etests/core/generate_test.go index 59b5414452..dd73bf67ff 100644 --- a/e2etests/core/generate_test.go +++ b/e2etests/core/generate_test.go @@ -5,7 +5,6 @@ package core_test import ( "fmt" - "path/filepath" "testing" "github.com/terramate-io/terramate/config" @@ -507,7 +506,7 @@ Hint: '+', '~' and '-' mean the file was created, changed and deleted, respectiv ) } - tmcli := NewCLI(t, filepath.Join(s.RootDir(), tcase.fromdir)) + tmcli := NewCLI(t, s.RootDir().Join(tcase.fromdir)) args := []string{"generate"} if tcase.detailedExitCode { args = append(args, "--detailed-exit-code") @@ -518,7 +517,7 @@ Hint: '+', '~' and '-' mean the file was created, changed and deleted, respectiv for _, wantFile := range tcase.want.files { t.Logf("checking if wanted file %q was created", wantFile.path) - gotFile := test.ReadFile(t, s.RootDir(), wantFile.path.String()) + gotFile := test.ReadFile(t, s.RootDir().String(), wantFile.path.String()) test.AssertGenCodeEquals(t, string(gotFile), wantFile.body.String()) } @@ -572,15 +571,15 @@ func TestE2EGenerateRespectsWorkingDirectory(t *testing.T) { configStr, ) - gencli := NewCLI(t, filepath.Join(s.RootDir(), generateWd)) + gencli := NewCLI(t, s.RootDir().Join(generateWd)) res := gencli.Run("generate") expected := RunExpected{ Stdout: nljoin(wantGenerate.Full()), } AssertRunResult(t, res, expected) - runcli := NewCLI(t, filepath.Join(s.RootDir(), runWd)) - res = runcli.Run("run", "--quiet", HelperPath, "stack-abs-path", s.RootDir()) + runcli := NewCLI(t, s.RootDir().Join(runWd)) + res = runcli.Run("run", "--quiet", HelperPath, "stack-abs-path", s.RootDir().String()) AssertRunResult(t, res, wantRun) }) } diff --git a/e2etests/core/list_git_test.go b/e2etests/core/list_git_test.go index 972f7932ee..4bf9868df6 100644 --- a/e2etests/core/list_git_test.go +++ b/e2etests/core/list_git_test.go @@ -6,7 +6,6 @@ package core_test import ( "fmt" "os" - "path/filepath" "runtime" "strings" "testing" @@ -61,7 +60,7 @@ func TestE2EListWithGitSubModules(t *testing.T) { } rootGit := rootSandbox.Git() - rootGit.AddSubmodule("sub", subSandbox.RootDir()) + rootGit.AddSubmodule("sub", subSandbox.RootDir().String()) rootGit.CommitAll("add submodule") @@ -104,7 +103,7 @@ func TestListDetectChangesInSubDirOfStack(t *testing.T) { git.CheckoutNew("change-the-stack") subfile.Write("# changed") - git.Add(stack.Path()) + git.Add(stack.Path().String()) git.Commit("stack changed") want := RunExpected{ @@ -123,7 +122,7 @@ func TestListIgnoresChangesInSymlinksOutsideStack(t *testing.T) { stack := s.CreateStack("stack") symlinkedFile := s.RootEntry().CreateFile("symlinked_dir/something.tf", "# nothing") - err := os.Symlink(symlinkedFile.HostPath(), filepath.Join(stack.Path(), "something.tf")) + err := os.Symlink(symlinkedFile.HostPath().String(), stack.Path().Join("something.tf").String()) assert.NoError(t, err) cli := NewCLI(t, s.RootDir()) @@ -141,9 +140,9 @@ func TestListIgnoresChangesInSymlinksOutsideStack(t *testing.T) { // Changing the symlink triggers change. symlinkedFile = s.RootEntry().CreateFile("symlinked_dir/other.tf", "# other") // link "something.tf" -> symlinked_dir/other.tf - newname := filepath.Join(stack.Path(), "something.tf") - assert.NoError(t, os.Remove(newname)) - err = os.Symlink(symlinkedFile.HostPath(), newname) + newname := stack.Path().Join("something.tf") + assert.NoError(t, os.Remove(newname.String())) + err = os.Symlink(symlinkedFile.HostPath().String(), newname.String()) assert.NoError(t, err) git.CommitAll("stack changed") @@ -178,7 +177,7 @@ terramate { git.CheckoutNew("change-the-stack") subsubfile.Write("# changed") - git.Add(stack.Path()) + git.Add(stack.Path().String()) git.Commit("stack changed") want := RunExpected{ @@ -200,7 +199,7 @@ func TestListChangedIgnoreDeletedStackDirectory(t *testing.T) { git.Push("main") git.CheckoutNew("deleted-stack") - test.RemoveAll(t, stack.Path()) + test.RemoveAll(t, stack.Path().String()) git.CommitAll("removed stack") @@ -213,8 +212,8 @@ func TestListChangedIgnoreDeletedNonStackDirectory(t *testing.T) { s := sandbox.New(t) s.CreateStack("stack") - toBeDeletedDir := filepath.Join(s.RootDir(), "to-be-deleted") - test.MkdirAll(t, toBeDeletedDir) + toBeDeletedDir := s.RootDir().Join("to-be-deleted") + test.MkdirAll(t, toBeDeletedDir.String()) test.WriteFile(t, toBeDeletedDir, "test.txt", "") cli := NewCLI(t, s.RootDir()) @@ -224,7 +223,7 @@ func TestListChangedIgnoreDeletedNonStackDirectory(t *testing.T) { git.CheckoutNew("deleted-diretory") - test.RemoveAll(t, toBeDeletedDir) + test.RemoveAll(t, toBeDeletedDir.String()) git.CommitAll("removed directory") AssertRun(t, cli.ListChangedStacks()) @@ -245,7 +244,7 @@ func TestListChangedDontIgnoreStackDeletedFiles(t *testing.T) { git.Push("main") git.CheckoutNew("deleted-file") - test.RemoveAll(t, file.HostPath()) + test.RemoveAll(t, file.HostPath().String()) git.CommitAll("removed file") @@ -270,7 +269,7 @@ func TestListChangedDontIgnoreStackDeletedDirs(t *testing.T) { git.Push("main") git.CheckoutNew("deleted-dir") - test.RemoveAll(t, toBeDeletedDir.Path()) + test.RemoveAll(t, toBeDeletedDir.Path().String()) git.CommitAll("removed dir") @@ -295,7 +294,7 @@ func TestListChangedDontIgnoreStackDeletedDirectories(t *testing.T) { git.Push("main") git.CheckoutNew("deleted-dir") - test.RemoveAll(t, testDir.Path()) + test.RemoveAll(t, testDir.Path().String()) git.CommitAll("removed directory") @@ -395,21 +394,21 @@ func TestGitGlobalConfigIsUsed(t *testing.T) { "f:.gitignore:*.test", }) - tempGlobalConfig := filepath.Join(s1.RootDir(), ".gitconfig") - tempGlobalGitignore := filepath.Join(s1.RootDir(), ".gitignore") + tempGlobalConfig := s1.RootDir().Join(".gitconfig") + tempGlobalGitignore := s1.RootDir().Join(".gitignore") gw, err := git.WithConfig(git.Config{ AllowPorcelain: true, WorkingDir: s1.RootDir(), Env: []string{ - "GIT_CONFIG_GLOBAL=" + tempGlobalConfig, + "GIT_CONFIG_GLOBAL=" + tempGlobalConfig.String(), }, }) assert.NoError(t, err) - _, err = gw.Exec("config", "--global", "core.excludesfile", tempGlobalGitignore) + _, err = gw.Exec("config", "--global", "core.excludesfile", tempGlobalGitignore.String()) assert.NoError(t, err) - t.Logf("config: %s", test.ReadFile(t, s1.RootDir(), ".gitconfig")) - t.Logf("ignore: %s", test.ReadFile(t, s1.RootDir(), ".gitignore")) + t.Logf("config: %s", test.ReadFile(t, s1.RootDir().String(), ".gitconfig")) + t.Logf("ignore: %s", test.ReadFile(t, s1.RootDir().String(), ".gitignore")) // code below creates a common case of changed files. // the directory sub/dir is ignored by the global .gitignore @@ -436,6 +435,6 @@ func TestGitGlobalConfigIsUsed(t *testing.T) { // use global config cli = NewCLI(t, repo.RootDir()) - cli.AppendEnv = append(cli.AppendEnv, "GIT_CONFIG_GLOBAL="+tempGlobalConfig) + cli.AppendEnv = append(cli.AppendEnv, "GIT_CONFIG_GLOBAL="+tempGlobalConfig.String()) AssertRun(t, cli.Run("run", "--quiet", "--", HelperPath, "true")) } diff --git a/e2etests/core/run_env_test.go b/e2etests/core/run_env_test.go index 8768a10f3c..fc25ce28ce 100644 --- a/e2etests/core/run_env_test.go +++ b/e2etests/core/run_env_test.go @@ -4,7 +4,6 @@ package core_test import ( - "path/filepath" "testing" . "github.com/terramate-io/terramate/e2etests/internal/runner" @@ -389,8 +388,8 @@ func TestRunEnv(t *testing.T) { t.Run(tc.name, func(t *testing.T) { s := sandbox.NoGit(t, true) s.BuildTree(tc.layout) - tmcli := NewCLI(t, filepath.Join(s.RootDir(), tc.wd), tc.env...) - args := []string{"run", "--quiet", "--", HelperPath, "env", s.RootDir()} + tmcli := NewCLI(t, s.RootDir().Join(tc.wd), tc.env...) + args := []string{"run", "--quiet", "--", HelperPath, "env", s.RootDir().String()} args = append(args, tc.args...) got := tmcli.Run(args...) AssertRunResult(t, got, tc.want) diff --git a/e2etests/core/run_parallel_test.go b/e2etests/core/run_parallel_test.go index a9b56c6b55..ec6b35b786 100644 --- a/e2etests/core/run_parallel_test.go +++ b/e2etests/core/run_parallel_test.go @@ -44,7 +44,7 @@ func TestParallelFibonacci(t *testing.T) { res := tmcli.Run("run", "--quiet", "--parallel=5", "--", HelperPath, "fibonacci") AssertRunResult(t, res, RunExpected{}) - b, err := os.ReadFile(s.RootDir() + fmt.Sprintf("/fib.%v/fib.txt", tc.FibN)) + b, err := os.ReadFile(s.RootDir().Join(fmt.Sprintf("fib.%v/fib.txt", tc.FibN)).String()) assert.NoError(t, err) got, err := strconv.ParseInt(string(b), 10, 64) assert.NoError(t, err) diff --git a/e2etests/core/run_test.go b/e2etests/core/run_test.go index 507451e87e..5062ebcd16 100644 --- a/e2etests/core/run_test.go +++ b/e2etests/core/run_test.go @@ -6,7 +6,6 @@ package core_test import ( "fmt" "os" - "path/filepath" "runtime" "sort" "strings" @@ -938,7 +937,7 @@ script "cmd" { wd := s.RootDir() if tc.workingDir != "" { - wd = filepath.Join(wd, tc.workingDir) + wd = wd.Join(tc.workingDir) } var filterArgs []string @@ -955,7 +954,7 @@ script "cmd" { "--quiet", "run", "-X", // disable all safeguards } runArgs = append(runArgs, filterArgs...) - runArgs = append(runArgs, "--", HelperPath, "stack-abs-path", s.RootDir()) + runArgs = append(runArgs, "--", HelperPath, "stack-abs-path", s.RootDir().String()) AssertRunResult(t, cli.Run(runArgs...), tc.want) if runtime.GOOS != "windows" { @@ -1585,7 +1584,7 @@ script "cmd" { baseArgs = append(baseArgs, "--no-tags", filter) } - cli := NewCLI(t, filepath.Join(s.RootDir(), tc.wd)) + cli := NewCLI(t, s.RootDir().Join(tc.wd)) runOrderArgs := append(baseArgs, "--quiet", "experimental", "run-order") AssertRunResult(t, cli.Run(runOrderArgs...), tc.want) @@ -1598,7 +1597,7 @@ script "cmd" { copiedBaseArgs := make([]string, len(baseArgs)) copy(copiedBaseArgs, baseArgs) - runArgs := append(copiedBaseArgs, "run", "--quiet", HelperPath, "stack-abs-path", s.RootDir()) + runArgs := append(copiedBaseArgs, "run", "--quiet", HelperPath, "stack-abs-path", s.RootDir().String()) AssertRunResult(t, cli.Run(runArgs...), tc.want) if runtime.GOOS != "windows" { @@ -1665,7 +1664,7 @@ func TestRunOrderNotChangedStackIgnored(t *testing.T) { mainTfFileName, ), RunExpected{Stdout: wantRun}) - cli = NewCLI(t, filepath.Join(s.RootDir(), "stack2")) + cli = NewCLI(t, s.RootDir().Join("stack2")) AssertRunResult(t, cli.Run( "run", "--changed", @@ -1754,7 +1753,7 @@ func TestRunIgnoresAfterBeforeStackRefsOutsideWorkingDirAndTagFilter(t *testing. git.CommitAll("first commit") assertRun := func(wd string, filter string, want string) { - cli := NewCLI(t, filepath.Join(s.RootDir(), wd)) + cli := NewCLI(t, s.RootDir().Join(wd)) var baseArgs []string if filter != "" { baseArgs = append(baseArgs, "--tags", filter) @@ -3072,7 +3071,7 @@ func TestRunLogsUserCommand(t *testing.T) { cli := NewCLI(t, s.RootDir()) cli.LogLevel = "info" - AssertRunResult(t, cli.Run("run", HelperPath, "cat", testfile.HostPath()), RunExpected{ + AssertRunResult(t, cli.Run("run", HelperPath, "cat", testfile.HostPath().String()), RunExpected{ StderrRegex: `Executing command`, }) } @@ -3219,7 +3218,7 @@ func TestRunWitCustomizedEnv(t *testing.T) { tm := NewCLI(t, s.RootDir(), clienv...) - res := tm.Run("run", HelperPath, "env", s.RootDir()) + res := tm.Run("run", HelperPath, "env", s.RootDir().String()) if res.Status != 0 { t.Errorf("unexpected status code %d", res.Status) t.Logf("stdout:\n%s", res.Stdout) @@ -3271,7 +3270,7 @@ func nljoin(stacks ...string) string { func testEnviron(t *testing.T) []string { tempHomeDir := test.TempDir(t) env := []string{ - fmt.Sprintf("%s="+tempHomeDir, cliconfig.DirEnv), + fmt.Sprintf("%s="+tempHomeDir.String(), cliconfig.DirEnv), "PATH=" + os.Getenv("PATH"), } if runtime.GOOS == "windows" { diff --git a/e2etests/core/run_unix_test.go b/e2etests/core/run_unix_test.go index 85bbd68e45..a65f8e4c08 100644 --- a/e2etests/core/run_unix_test.go +++ b/e2etests/core/run_unix_test.go @@ -8,7 +8,6 @@ package core_test import ( "io" "os" - "path/filepath" "testing" "github.com/madlambda/spells/assert" @@ -47,11 +46,11 @@ func TestRunLookPathFromStackEnviron(t *testing.T) { srcPerm := srcStat.Mode().Perm() - tdir := filepath.Join(s.RootDir(), "bin") - test.MkdirAll2(t, tdir, 0777) - dstFilename := filepath.Join(tdir, programName) + tdir := s.RootDir().Join("bin") + test.MkdirAll2(t, tdir.String(), 0777) + dstFilename := tdir.Join(programName) - dstFile, err := os.Create(dstFilename) + dstFile, err := os.Create(dstFilename.String()) assert.NoError(t, err) _, err = io.Copy(dstFile, srcFile) diff --git a/e2etests/core/safeguard_test.go b/e2etests/core/safeguard_test.go index 0441912ac2..6b0e2d5ffe 100644 --- a/e2etests/core/safeguard_test.go +++ b/e2etests/core/safeguard_test.go @@ -47,7 +47,7 @@ func TestSafeguardsUsages(t *testing.T) { "--disable-safeguards=git-untracked,git-out-of-sync", HelperPath, "cat", - file.HostPath(), + file.HostPath().String(), ), RunExpected{Stdout: fileContents}) }) @@ -61,7 +61,7 @@ func TestSafeguardsUsages(t *testing.T) { "--disable-safeguards=git-out-of-sync", HelperPath, "cat", - file.HostPath(), + file.HostPath().String(), ), RunExpected{Stdout: fileContents}) }) @@ -75,7 +75,7 @@ func TestSafeguardsUsages(t *testing.T) { "-X", HelperPath, "cat", - file.HostPath(), + file.HostPath().String(), ), RunExpected{ Status: 1, StderrRegex: string(clitest.ErrSafeguardKeywordValidation), @@ -91,7 +91,7 @@ func TestSafeguardsUsages(t *testing.T) { "--disable-safeguards=all,none", HelperPath, "cat", - file.HostPath(), + file.HostPath().String(), ), RunExpected{ Status: 1, StderrRegex: string(clitest.ErrSafeguardKeywordValidation), @@ -108,7 +108,7 @@ func TestSafeguardsUsages(t *testing.T) { "--disable-safeguards=all,none", HelperPath, "cat", - file.HostPath(), + file.HostPath().String(), ), RunExpected{ Status: 1, StderrRegex: string(clitest.ErrSafeguardKeywordValidation), @@ -180,7 +180,7 @@ func TestSafeguardCheckRemoteFailsOnRunIfRemoteMainIsOutdated(t *testing.T) { "run", HelperPath, "cat", - mainTfFile.HostPath(), + mainTfFile.HostPath().String(), ), wantRes) AssertRunResult(t, ts.Run( @@ -188,7 +188,7 @@ func TestSafeguardCheckRemoteFailsOnRunIfRemoteMainIsOutdated(t *testing.T) { "--changed", HelperPath, "cat", - mainTfFile.HostPath(), + mainTfFile.HostPath().String(), ), wantRes) } @@ -238,7 +238,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { "run", HelperPath, "cat", - file.HostPath(), + file.HostPath().String(), ), RunExpected{ Status: 1, StderrRegex: string(cli.ErrCurrentHeadIsOutOfDate), @@ -253,7 +253,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { "--disable-check-git-remote", HelperPath, "cat", - file.HostPath(), + file.HostPath().String(), ), RunExpected{Stdout: fileContents}) }) @@ -265,7 +265,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { "--disable-safeguards=git-out-of-sync", HelperPath, "cat", - file.HostPath(), + file.HostPath().String(), ), RunExpected{Stdout: fileContents}) }) @@ -277,7 +277,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { "--disable-safeguards=git", HelperPath, "cat", - file.HostPath(), + file.HostPath().String(), ), RunExpected{Stdout: fileContents}) }) @@ -289,7 +289,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { "-X", HelperPath, "cat", - file.HostPath(), + file.HostPath().String(), ), RunExpected{Stdout: fileContents}) }) @@ -298,7 +298,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { tmcli.AppendEnv = append(tmcli.AppendEnv, "TM_DISABLE_CHECK_GIT_REMOTE=true") AssertRunResult(t, tmcli.Run("run", "--quiet", HelperPath, - "cat", file.HostPath()), RunExpected{ + "cat", file.HostPath().String()), RunExpected{ Stdout: fileContents, }) }) @@ -308,7 +308,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { tmcli.AppendEnv = append(tmcli.AppendEnv, "TM_DISABLE_CHECK_GIT_REMOTE=1") AssertRunResult(t, tmcli.Run("run", "--quiet", HelperPath, - "cat", file.HostPath()), RunExpected{ + "cat", file.HostPath().String()), RunExpected{ Stdout: fileContents, }) }) @@ -318,7 +318,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { tmcli.AppendEnv = append(tmcli.AppendEnv, "TM_DISABLE_SAFEGUARDS=git-out-of-sync") AssertRunResult(t, tmcli.Run("run", "--quiet", HelperPath, - "cat", file.HostPath()), RunExpected{ + "cat", file.HostPath().String()), RunExpected{ Stdout: fileContents, }) }) @@ -328,7 +328,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { tmcli.AppendEnv = append(tmcli.AppendEnv, "TM_DISABLE_SAFEGUARDS=git") AssertRunResult(t, tmcli.Run("run", "--quiet", HelperPath, - "cat", file.HostPath()), RunExpected{ + "cat", file.HostPath().String()), RunExpected{ Stdout: fileContents, }) }) @@ -356,7 +356,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { "run", HelperPath, "cat", - file.HostPath(), + file.HostPath().String(), ), RunExpected{ Status: 1, @@ -384,7 +384,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { git.Commit("commit root config") AssertRunResult(t, tmcli.Run("run", "--quiet", HelperPath, - "cat", file.HostPath()), RunExpected{ + "cat", file.HostPath().String()), RunExpected{ Stdout: fileContents, }) }) @@ -407,7 +407,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { git.Commit("commit root config") AssertRunResult(t, tmcli.Run("run", "--quiet", HelperPath, - "cat", file.HostPath()), RunExpected{ + "cat", file.HostPath().String()), RunExpected{ Stdout: fileContents, }) }) @@ -436,7 +436,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { "--disable-safeguards=none", HelperPath, "cat", - file.HostPath(), + file.HostPath().String(), ), RunExpected{ Status: 1, @@ -466,7 +466,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { "--disable-safeguards=none", HelperPath, "cat", - file.HostPath(), + file.HostPath().String(), ), RunExpected{ Status: 1, StderrRegex: string(cli.ErrCurrentHeadIsOutOfDate), @@ -497,7 +497,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { "run", HelperPath, "cat", - file.HostPath(), + file.HostPath().String(), ), RunExpected{ Status: 1, @@ -530,7 +530,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { "--disable-check-git-remote", HelperPath, "cat", - file.HostPath(), + file.HostPath().String(), ), RunExpected{Stdout: fileContents}) }) @@ -559,7 +559,7 @@ func TestSafeguardCheckRemoteDisabled(t *testing.T) { "--disable-safeguards=git-out-of-sync", HelperPath, "cat", - file.HostPath(), + file.HostPath().String(), ), RunExpected{Stdout: fileContents}) }) } @@ -592,7 +592,7 @@ func TestSafeguardCheckRemoteDisabledWorksWithoutNetworking(t *testing.T) { "run", HelperPath, "cat", - stackFile.HostPath(), + stackFile.HostPath().String(), ), RunExpected{ Status: 1, StderrRegex: "Could not resolve host: non-existent", @@ -603,7 +603,7 @@ func TestSafeguardCheckRemoteDisabledWorksWithoutNetworking(t *testing.T) { "--disable-check-git-remote", HelperPath, "cat", - stackFile.HostPath(), + stackFile.HostPath().String(), ), RunExpected{ Stdout: fileContents, }) @@ -624,7 +624,7 @@ func TestSafeguardCheckRemoteDisjointBranchesAreUnreachable(t *testing.T) { git.CommitAll("first commit") bare := sandbox.New(t) - git.SetRemoteURL("origin", string(uri.File(bare.Git().BareRepoAbsPath()))) + git.SetRemoteURL("origin", string(uri.File(bare.Git().BareRepoAbsPath().String()))) tm := NewCLI(t, s.RootDir()) @@ -632,7 +632,7 @@ func TestSafeguardCheckRemoteDisjointBranchesAreUnreachable(t *testing.T) { "run", HelperPath, "cat", - stackFile.HostPath(), + stackFile.HostPath().String(), ), RunExpected{ Status: 1, StderrRegex: string(cli.ErrCurrentHeadIsOutOfDate), diff --git a/e2etests/core/script_info_test.go b/e2etests/core/script_info_test.go index 27e34429a0..55d3779967 100644 --- a/e2etests/core/script_info_test.go +++ b/e2etests/core/script_info_test.go @@ -5,7 +5,6 @@ package core_test import ( "fmt" - "path/filepath" "strings" "testing" @@ -194,7 +193,7 @@ Jobs: t.Run(name, func(t *testing.T) { wd := s.RootDir() if tc.dir != "" { - wd = filepath.Join(wd, tc.dir) + wd = wd.Join(tc.dir) } cli := NewCLI(t, wd) diff --git a/e2etests/core/script_list_test.go b/e2etests/core/script_list_test.go index bd92867c7d..086259b77d 100644 --- a/e2etests/core/script_list_test.go +++ b/e2etests/core/script_list_test.go @@ -5,7 +5,6 @@ package core_test import ( "fmt" - "path/filepath" "testing" . "github.com/terramate-io/terramate/e2etests/internal/runner" @@ -141,7 +140,7 @@ func TestScriptList(t *testing.T) { tc := tc wd := s.RootDir() if tc.dir != "" { - wd = filepath.Join(wd, tc.dir) + wd = wd.Join(tc.dir) } cli := NewCLI(t, wd) diff --git a/e2etests/core/script_run_test.go b/e2etests/core/script_run_test.go index 3a06749ccc..0c1771be42 100644 --- a/e2etests/core/script_run_test.go +++ b/e2etests/core/script_run_test.go @@ -5,7 +5,6 @@ package core_test import ( "os" - "path/filepath" "testing" . "github.com/terramate-io/terramate/e2etests/internal/runner" @@ -478,7 +477,7 @@ func TestScriptRun(t *testing.T) { wd := s.RootDir() if tc.workingDir != "" { - wd = filepath.Join(wd, tc.workingDir) + wd = wd.Join(tc.workingDir) } // required because `terramate run-script` requires a clean repo. diff --git a/e2etests/internal/runner/runner.go b/e2etests/internal/runner/runner.go index e9e57df088..de7edb039d 100644 --- a/e2etests/internal/runner/runner.go +++ b/e2etests/internal/runner/runner.go @@ -6,7 +6,7 @@ package runner import ( "bytes" "fmt" - "os" + stdos "os" "os/exec" "path/filepath" "regexp" @@ -19,6 +19,7 @@ import ( "github.com/golang-jwt/jwt" "github.com/google/go-cmp/cmp" "github.com/madlambda/spells/assert" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/stack/trigger" "github.com/terramate-io/terramate/test" ) @@ -31,12 +32,12 @@ type ( // CLI is a Terramate CLI runner. CLI struct { t *testing.T - Chdir string + Chdir os.Path LogLevel string environ []string AppendEnv []string - userDir string + userDir os.Path } // RunResult specify the result of executing the cli. @@ -72,7 +73,7 @@ type ( ) // NewCLI creates a new runner for the CLI. -func NewCLI(t *testing.T, chdir string, env ...string) CLI { +func NewCLI(t *testing.T, chdir os.Path, env ...string) CLI { if toolsetTestPath == "" { panic("runner is not initialized: use runner.Setup()") } @@ -82,7 +83,7 @@ func NewCLI(t *testing.T, chdir string, env ...string) CLI { } if len(env) == 0 { // by default, it's assumed human mode - env = RemoveEnv(os.Environ(), "CI", "GITHUB_ACTIONS", "GITHUB_TOKEN") + env = RemoveEnv(stdos.Environ(), "CI", "GITHUB_ACTIONS", "GITHUB_TOKEN") } // environments below are never used in automation. env = RemoveEnv(env, "TMC_API_HOST", "TMC_API_IDP_KEY") @@ -99,16 +100,16 @@ func NewCLI(t *testing.T, chdir string, env ...string) CLI { } // custom cliconfig file tm.userDir = test.TempDir(t) - cliConfigPath := test.WriteFile(t, tm.userDir, "terramate.rc", fmt.Sprintf(testCliConfigFormat, strings.Replace(tm.userDir, "\\", "\\\\", -1))) + cliConfigPath := test.WriteFile(t, tm.userDir, "terramate.rc", fmt.Sprintf(testCliConfigFormat, strings.Replace(tm.userDir.String(), "\\", "\\\\", -1))) env = append(env, - "TM_CLI_CONFIG_FILE="+cliConfigPath, + "TM_CLI_CONFIG_FILE="+cliConfigPath.String(), ) tm.environ = env return tm } // NewInteropCLI creates a new runner CLI suited for interop tests. -func NewInteropCLI(t *testing.T, chdir string, env ...string) CLI { +func NewInteropCLI(t *testing.T, chdir os.Path, env ...string) CLI { if toolsetTestPath == "" { panic("runner is not initialized: use runner.Setup()") } @@ -117,7 +118,7 @@ func NewInteropCLI(t *testing.T, chdir string, env ...string) CLI { Chdir: chdir, } if len(env) == 0 { - env = os.Environ() + env = stdos.Environ() } env = append(env, "CHECKPOINT_DISABLE=1") tm.environ = env @@ -192,7 +193,7 @@ func (tm CLI) NewCmd(args ...string) *Cmd { allargs := []string{} if tm.Chdir != "" { - allargs = append(allargs, "--chdir", tm.Chdir) + allargs = append(allargs, "--chdir", tm.Chdir.String()) } loglevel := tm.LogLevel diff --git a/errors/error.go b/errors/error.go index 25cac67fb1..081ed409c8 100644 --- a/errors/error.go +++ b/errors/error.go @@ -149,7 +149,7 @@ func E(args ...interface{}) *Error { start := arg.Start() end := arg.End() e.FileRange = hcl.Range{ - Filename: arg.HostPath(), + Filename: arg.HostPath().String(), Start: hcl.Pos{ Line: start.Line(), Column: start.Column(), diff --git a/fs/copy.go b/fs/copy.go index ed8367b495..a3a02e5ef6 100644 --- a/fs/copy.go +++ b/fs/copy.go @@ -5,28 +5,28 @@ package fs import ( "io" - "os" - "path/filepath" + stdos "os" "github.com/rs/zerolog/log" "github.com/terramate-io/terramate/errors" + "github.com/terramate-io/terramate/os" ) // CopyFilterFunc filters which files/dirs will be copied by CopyDir. // If the function returns true, the file/dir is copied. // If it returns false, the file/dir is ignored. -type CopyFilterFunc func(path string, entry os.DirEntry) bool +type CopyFilterFunc func(path os.Path, entry stdos.DirEntry) bool // CopyDir will copy srcdir to destdir. // It will copy all dirs and files recursively. // The destdir provided does not need to exist, it will be created. // The provided filter function allows to filter which files/directories get copied. -func CopyDir(destdir, srcdir string, filter CopyFilterFunc) error { +func CopyDir(destdir, srcdir os.Path, filter CopyFilterFunc) error { const ( createDirMode = 0755 ) - entries, err := os.ReadDir(srcdir) + entries, err := stdos.ReadDir(srcdir.String()) if err != nil { return errors.E(err, "reading src dir") } @@ -36,7 +36,7 @@ func CopyDir(destdir, srcdir string, filter CopyFilterFunc) error { if createdDir { return nil } - if err := os.MkdirAll(destdir, createDirMode); err != nil { + if err := stdos.MkdirAll(destdir.String(), createDirMode); err != nil { return errors.E(err, "creating dest dir") } createdDir = true @@ -48,8 +48,8 @@ func CopyDir(destdir, srcdir string, filter CopyFilterFunc) error { continue } - srcpath := filepath.Join(srcdir, entry.Name()) - destpath := filepath.Join(destdir, entry.Name()) + srcpath := srcdir.Join(entry.Name()) + destpath := destdir.Join(entry.Name()) if entry.IsDir() { if err := CopyDir(destpath, srcpath, filter); err != nil { @@ -73,19 +73,19 @@ func CopyDir(destdir, srcdir string, filter CopyFilterFunc) error { } // CopyAll copies all files from dstdir into srcdir. -func CopyAll(dstdir, srcdir string) error { - return CopyDir(dstdir, srcdir, func(_ string, _ os.DirEntry) bool { +func CopyAll(dstdir, srcdir os.Path) error { + return CopyDir(dstdir, srcdir, func(_ os.Path, _ stdos.DirEntry) bool { return true }) } -func copyFile(destfile, srcfile string) error { - src, err := os.Open(srcfile) +func copyFile(destfile, srcfile os.Path) error { + src, err := stdos.Open(srcfile.String()) if err != nil { return errors.E(err, "opening source file") } defer closeFile(src) - dest, err := os.Create(destfile) + dest, err := stdos.Create(destfile.String()) if err != nil { return errors.E(err, "creating dest file") } @@ -94,7 +94,7 @@ func copyFile(destfile, srcfile string) error { return err } -func closeFile(file *os.File) { +func closeFile(file *stdos.File) { err := file.Close() if err != nil { log.Warn(). diff --git a/fs/copy_test.go b/fs/copy_test.go index e59b57a2ce..37c7cde940 100644 --- a/fs/copy_test.go +++ b/fs/copy_test.go @@ -4,13 +4,13 @@ package fs_test import ( - "os" - "path/filepath" + stdos "os" "testing" "github.com/madlambda/spells/assert" "github.com/rs/zerolog" "github.com/terramate-io/terramate/fs" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/test" "github.com/terramate-io/terramate/test/sandbox" ) @@ -30,19 +30,19 @@ func TestCopyIfAllFilesAreFilteredDirIsNotCreated(t *testing.T) { }) destdir := test.TempDir(t) - err := fs.CopyDir(destdir, s.RootDir(), func(_ string, entry os.DirEntry) bool { + err := fs.CopyDir(destdir, s.RootDir(), func(_ os.Path, entry stdos.DirEntry) bool { return entry.Name() != "notcopy" && entry.Name() != "root.config.tm" }) assert.NoError(t, err) - entries, err := os.ReadDir(destdir) + entries, err := stdos.ReadDir(destdir.String()) assert.NoError(t, err) assert.EqualInts(t, 1, len(entries)) assert.EqualStrings(t, "test", entries[0].Name()) - entries, err = os.ReadDir(filepath.Join(destdir, "test")) + entries, err = stdos.ReadDir(destdir.Join("test").String()) assert.NoError(t, err) assert.EqualInts(t, 3, len(entries)) diff --git a/fs/fs.go b/fs/fs.go index 44d7beaff9..6300fe7e85 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -4,16 +4,17 @@ package fs import ( - "os" + stdos "os" "strings" "github.com/terramate-io/terramate/errors" + "github.com/terramate-io/terramate/os" ) // ListTerramateFiles returns a list of terramate related files from the // directory dir. -func ListTerramateFiles(dir string) (filenames []string, err error) { - f, err := os.Open(dir) +func ListTerramateFiles(dir os.Path) (filenames os.Paths, err error) { + f, err := stdos.Open(dir.String()) if err != nil { return nil, errors.E(err, "opening directory %s for reading file entries", dir) } @@ -27,23 +28,21 @@ func ListTerramateFiles(dir string) (filenames []string, err error) { return nil, errors.E(err, "reading dir to list Terramate files") } - files := []string{} - + files := os.Paths{} for _, entry := range dirEntries { fname := entry.Name() if entry.IsDir() || !isTerramateFile(fname) { continue } - files = append(files, fname) + files = append(files, dir.Join(fname)) } - return files, nil } // ListTerramateDirs lists Terramate dirs, which are any dirs // except ones starting with ".". -func ListTerramateDirs(dir string) ([]string, error) { - f, err := os.Open(dir) +func ListTerramateDirs(dir os.Path) ([]string, error) { + f, err := stdos.Open(dir.String()) if err != nil { return nil, errors.E(err, "opening directory %s for reading file entries", dir) } diff --git a/fs/fs_bench_test.go b/fs/fs_bench_test.go index 0838619184..7eb4838479 100644 --- a/fs/fs_bench_test.go +++ b/fs/fs_bench_test.go @@ -5,12 +5,12 @@ package fs_test import ( "fmt" - "os" - "path/filepath" + stdos "os" "testing" "github.com/madlambda/spells/assert" "github.com/terramate-io/terramate/fs" + "github.com/terramate-io/terramate/os" ) func BenchmarkListFiles(b *testing.B) { @@ -18,24 +18,24 @@ func BenchmarkListFiles(b *testing.B) { const otherFiles = 50 const ndirs = 50 b.StopTimer() - dir := b.TempDir() + dir := os.NewHostPath(b.TempDir()) for i := 0; i < ndirs; i++ { - p := filepath.Join(dir, fmt.Sprintf("dir_%d", i)) - err := os.MkdirAll(p, 0644) + p := dir.Join(fmt.Sprintf("dir_%d", i)) + err := stdos.MkdirAll(p.String(), 0644) assert.NoError(b, err) } for i := 0; i < tmFiles; i++ { - p := filepath.Join(dir, fmt.Sprintf("terramate_%d.tm", i)) - f, err := os.Create(p) + p := dir.Join(fmt.Sprintf("terramate_%d.tm", i)) + f, err := stdos.Create(p.String()) assert.NoError(b, err) assert.NoError(b, f.Close()) } for i := 0; i < otherFiles; i++ { - p := filepath.Join(dir, fmt.Sprintf("other_%d.txt", i)) - f, err := os.Create(p) + p := dir.Join(fmt.Sprintf("other_%d.txt", i)) + f, err := stdos.Create(p.String()) assert.NoError(b, err) assert.NoError(b, f.Close()) } diff --git a/generate/generate.go b/generate/generate.go index 59ee56f1f5..dccdc2d020 100644 --- a/generate/generate.go +++ b/generate/generate.go @@ -6,7 +6,7 @@ package generate import ( "fmt" "io/fs" - "os" + stdos "os" "path" "path/filepath" "sort" @@ -24,6 +24,7 @@ import ( "github.com/terramate-io/terramate/hcl" "github.com/terramate-io/terramate/hcl/eval" "github.com/terramate-io/terramate/hcl/info" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/stack" "github.com/terramate-io/terramate/stdlib" @@ -123,7 +124,7 @@ func Load(root *config.Root, vendorDir project.Path) ([]LoadResult, error) { continue } res := LoadResult{Dir: dircfg.Dir()} - evalctx := eval.NewContext(stdlib.Functions(dircfg.HostDir(), root.Tree().Node.Experiments())) + evalctx := eval.NewContext(stdlib.Functions(dircfg.Path(), root.Tree().Node.Experiments())) var generated []GenFile for _, block := range dircfg.Node.Generate.Files { @@ -240,13 +241,13 @@ func doStackGeneration( continue } - err = validateStackGeneratedFiles(root, cfg.HostDir(), generated) + err = validateStackGeneratedFiles(root, cfg.Path(), generated) if err != nil { report.addFailure(cfg.Dir(), err) continue } - allFiles, err := allStackGeneratedFiles(root, cfg.HostDir(), generated) + allFiles, err := allStackGeneratedFiles(root, cfg.Path(), generated) if err != nil { report.addFailure(cfg.Dir(), errors.E(err, "listing all generated files")) continue @@ -258,7 +259,7 @@ func doStackGeneration( for _, file := range generated { filename := file.Label() - path := filepath.Join(cfg.HostDir(), filename) + path := cfg.Path().Join(filename) logger := logger.With(). Str("filename", filename). Logger() @@ -309,8 +310,8 @@ func doStackGeneration( stackReport.addDeletedFile(filename) - path := filepath.Join(cfg.HostDir(), filename) - err = os.Remove(path) + path := cfg.Path().Join(filename) + err = stdos.Remove(path.String()) if err != nil { report.addFailure(cfg.Dir(), errors.E("removing file %s", filename)) continue @@ -332,7 +333,7 @@ func doRootGeneration(root *config.Root, tree *config.Tree) Report { Logger() report := Report{} - evalctx := eval.NewContext(stdlib.Functions(root.HostDir(), root.Tree().Node.Experiments())) + evalctx := eval.NewContext(stdlib.Functions(root.Path(), root.Tree().Node.Experiments())) evalctx.SetNamespace("terramate", root.Runtime()) var files []GenFile @@ -408,21 +409,21 @@ func doRootGeneration(root *config.Root, tree *config.Tree) Report { return report } -func handleAsserts(rootdir string, dir string, asserts []config.Assert) error { +func handleAsserts(rootdir os.Path, dir os.Path, asserts []config.Assert) error { logger := log.With(). Str("action", "generate.handleAsserts()"). - Str("dir", dir). + Stringer("dir", dir). Logger() errs := errors.L() for _, assert := range asserts { if !assert.Assertion { assertRange := assert.Range - assertRange.Filename = project.PrjAbsPath(rootdir, assert.Range.Filename).String() + assertRange.Filename = project.PrjAbsPath(rootdir, os.NewHostPath(assert.Range.Filename)).String() if assert.Warning { log.Warn(). Stringer("origin", assertRange). Str("msg", assert.Message). - Str("dir", dir). + Stringer("dir", dir). Msg("assertion failed") } else { msg := fmt.Sprintf("%s: %s", assertRange, assert.Message) @@ -456,7 +457,7 @@ func handleAsserts(rootdir string, dir string, asserts []config.Assert) error { // // When called with a dir that is a stack this function will list all generated // files that are owned by the stack, since it won't search inside any child stacks. -func ListStackGenFiles(root *config.Root, dir string) ([]string, error) { +func ListStackGenFiles(root *config.Root, dir os.Path) ([]string, error) { pendingSubDirs := []string{""} genfiles := []string{} @@ -464,8 +465,8 @@ processSubdirs: for len(pendingSubDirs) > 0 { relSubdir := pendingSubDirs[0] pendingSubDirs = pendingSubDirs[1:] - absSubdir := filepath.Join(dir, relSubdir) - entries, err := os.ReadDir(absSubdir) + absSubdir := dir.Join(relSubdir) + entries, err := stdos.ReadDir(absSubdir.String()) if err != nil { return nil, errors.E(err) } @@ -484,7 +485,7 @@ processSubdirs: continue } - isStack := config.IsStack(root, filepath.Join(absSubdir, entry.Name())) + isStack := config.IsStack(root, absSubdir.Join(entry.Name())) if isStack { continue } @@ -503,8 +504,8 @@ processSubdirs: continue } - file := filepath.Join(absSubdir, entry.Name()) - data, err := os.ReadFile(file) + file := absSubdir.Join(entry.Name()) + data, err := stdos.ReadFile(file.String()) if err != nil { return nil, errors.E(err, "checking if file is generated %q", file) } @@ -576,7 +577,7 @@ func DetectOutdated(root *config.Root, target *config.Tree, vendorDir project.Pa logger.Debug().Msg("checking for orphaned files") - orphanedFiles, err := ListStackGenFiles(root, target.HostDir()) + orphanedFiles, err := ListStackGenFiles(root, target.Path()) if err != nil { errs.Append(err) } @@ -604,7 +605,7 @@ func stackContextOutdated(root *config.Root, cfg *config.Tree, vendorDir project Stringer("stack", cfg.Dir()). Logger() - cfgpath := cfg.HostDir() + cfgpath := cfg.Path() generated, err := loadStackCodeCfgs(root, cfg, vendorDir, nil) if err != nil { @@ -635,8 +636,7 @@ func stackContextOutdated(root *config.Root, cfg *config.Tree, vendorDir project // rootContextOutdated will verify if the given directory has outdated code for context=root blocks // and return the list of outdated files. func rootContextOutdated(root *config.Root, cfg *config.Tree) ([]string, error) { - cfgpath := cfg.HostDir() - + cfgpath := cfg.Path() generated, err := loadRootCodeCfgs(root, cfg) if err != nil { return nil, err @@ -652,10 +652,10 @@ func rootContextOutdated(root *config.Root, cfg *config.Tree) ([]string, error) return outdatedFiles.slice(), nil } -func updateOutdatedFiles(root *config.Root, cfgpath string, generated []GenFile, outdatedFiles *stringSet) error { +func updateOutdatedFiles(root *config.Root, cfgpath os.Path, generated []GenFile, outdatedFiles *stringSet) error { logger := log.With(). Str("action", "generate.updateOutdatedFiles"). - Str("stack", cfgpath). + Stringer("stack", cfgpath). Logger() // So we can properly check blocks with condition false/true in any order @@ -666,14 +666,14 @@ func updateOutdatedFiles(root *config.Root, cfgpath string, generated []GenFile, Str("label", genfile.Label()). Logger() - var targetpath string + var targetpath os.Path var filename string if genfile.Context() == "root" { filename = genfile.Label()[1:] - targetpath = filepath.Join(root.HostDir(), filename) + targetpath = root.Path().Join(filename) } else { filename = genfile.Label() - targetpath = filepath.Join(cfgpath, filename) + targetpath = cfgpath.Join(filename) } currentCode, codeFound, err := readFile(targetpath) @@ -727,7 +727,7 @@ func updateOutdatedFiles(root *config.Root, cfgpath string, generated []GenFile, return nil } -func writeGeneratedCode(root *config.Root, target string, genfile GenFile) error { +func writeGeneratedCode(root *config.Root, target os.Path, genfile GenFile) error { body := genfile.Header() + genfile.Body() if genfile.Header() != "" { @@ -739,14 +739,14 @@ func writeGeneratedCode(root *config.Root, target string, genfile GenFile) error } } - if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil { + if err := stdos.MkdirAll(target.Dir().String(), 0755); err != nil { return err } - return os.WriteFile(target, []byte(body), 0666) + return stdos.WriteFile(target.String(), []byte(body), 0666) } -func checkFileCanBeOverwritten(root *config.Root, path string) error { +func checkFileCanBeOverwritten(root *config.Root, path os.Path) error { _, _, err := readGeneratedFile(root, path) return err } @@ -758,7 +758,7 @@ func checkFileCanBeOverwritten(root *config.Root, path string) error { // The returned boolean indicates if the file exists, so the contents of // the file + true is returned if a file is found, but if no file is found // it will return an empty string and false indicating that the file doesn't exist. -func readGeneratedFile(root *config.Root, path string) (string, bool, error) { +func readGeneratedFile(root *config.Root, path os.Path) (string, bool, error) { data, found, err := readFile(path) if err != nil { return "", false, err @@ -781,8 +781,9 @@ func readGeneratedFile(root *config.Root, path string) (string, bool, error) { // The returned boolean indicates if the file exists, so the contents of // the file + true is returned if a file is found, but if no file is found // it will return an empty string and false indicating that the file doesn't exist. -func readFile(path string) (string, bool, error) { - _, err := os.Stat(path) +func readFile(path os.Path) (string, bool, error) { + pathstr := path.String() + _, err := stdos.Stat(pathstr) if err != nil { if errors.Is(err, fs.ErrNotExist) { return "", false, nil @@ -790,7 +791,7 @@ func readFile(path string) (string, bool, error) { return "", false, err } - data, err := os.ReadFile(path) + data, err := stdos.ReadFile(pathstr) if err != nil { return "", false, err } @@ -800,7 +801,7 @@ func readFile(path string) (string, bool, error) { func allStackGeneratedFiles( root *config.Root, - dir string, + dir os.Path, genfiles []GenFile, ) (map[string]string, error) { allFiles := map[string]string{} @@ -821,10 +822,10 @@ func allStackGeneratedFiles( } for _, filename := range files { - path := filepath.Join(dir, filename) - body, err := os.ReadFile(path) + path := dir.Join(filename) + body, err := stdos.ReadFile(path.String()) if err != nil { - if os.IsNotExist(err) { + if stdos.IsNotExist(err) { continue } return nil, errors.E(err, "reading generated file") @@ -873,11 +874,11 @@ func generateRootFiles(root *config.Root, genfiles []GenFile, report *Report) { logger.Debug().Msg("reading the content of the file on disk") - abspath := filepath.Join(root.HostDir(), label) + abspath := root.Path().Join(label) dir := path.Dir(label) - body, err := os.ReadFile(abspath) + body, err := stdos.ReadFile(abspath.String()) if err != nil { - if os.IsNotExist(err) { + if stdos.IsNotExist(err) { logger.Debug().Msg("file do not exists") continue @@ -897,15 +898,15 @@ func generateRootFiles(root *config.Root, genfiles []GenFile, report *Report) { for label := range mustDeleteFiles { logger := logger.With().Str("file", label).Logger() - abspath := filepath.Join(root.HostDir(), label) - _, err := os.Lstat(abspath) + abspath := root.Path().Join(label).String() + _, err := stdos.Lstat(abspath) if err == nil { logger.Debug().Msg("deleting file") dirReport := dirReport{} dir := path.Dir(label) - err := os.Remove(abspath) + err := stdos.Remove(abspath) if err != nil { dirReport.err = errors.E(err, "deleting file") } else { @@ -923,7 +924,6 @@ func generateRootFiles(root *config.Root, genfiles []GenFile, report *Report) { logger.Debug().Msg("generating file (if needed)") - abspath := filepath.Join(root.HostDir(), label) filename := path.Base(label) dir := project.NewPath(path.Dir(label)) body := genfile.Header() + genfile.Body() @@ -936,6 +936,7 @@ func generateRootFiles(root *config.Root, genfiles []GenFile, report *Report) { Bool("fileChanged", body != diskContent). Msg("writing file") + abspath := root.Path().Join(label) err := writeGeneratedCode(root, abspath, genfile) if err != nil { dirReport.err = errors.E(err, "saving file %s", label) @@ -984,7 +985,7 @@ func hasGenHCLHeader(commentStyle genhcl.CommentStyle, code string) bool { return false } -func validateStackGeneratedFiles(root *config.Root, stackpath string, generated []GenFile) error { +func validateStackGeneratedFiles(root *config.Root, stackpath os.Path, generated []GenFile) error { errs := errors.L() for _, file := range generated { @@ -1014,12 +1015,12 @@ func validateStackGeneratedFiles(root *config.Root, stackpath string, generated continue } - abspath := filepath.Join(stackpath, relpath) - destdir := filepath.Dir(abspath) + abspath := stackpath.Join(relpath) + destdir := abspath.Dir() // We need to check that destdir, or any of its parents, is not a symlink, a stack or a dotdir. - for strings.HasPrefix(destdir, stackpath) && destdir != stackpath { - dirname := filepath.Base(destdir) + for destdir.HasPrefix(stackpath.String()) && destdir != stackpath { + dirname := destdir.Base() if dirname[0] == '.' { errs.Append(errors.E( ErrInvalidGenBlockLabel, @@ -1028,10 +1029,10 @@ func validateStackGeneratedFiles(root *config.Root, stackpath string, generated file.Label(), )) } - info, err := os.Lstat(destdir) + info, err := stdos.Lstat(destdir.String()) if err != nil { if errors.Is(err, fs.ErrNotExist) { - destdir = filepath.Dir(destdir) + destdir = destdir.Dir() continue } errs.Append(errors.E(ErrInvalidGenBlockLabel, err, @@ -1053,10 +1054,10 @@ func validateStackGeneratedFiles(root *config.Root, stackpath string, generated file.Range(), "%s: generates code inside another stack %s", file.Label(), - project.PrjAbsPath(root.HostDir(), destdir))) + project.PrjAbsPath(root.Path(), destdir))) break } - destdir = filepath.Dir(destdir) + destdir = destdir.Dir() } } @@ -1072,22 +1073,22 @@ func validateRootGenerateBlock(root *config.Root, block hcl.GenFileBlock) error ) } - abspath := filepath.Join(root.HostDir(), filepath.FromSlash(target)) - abspath = filepath.Clean(abspath) - destdir := filepath.Dir(abspath) + abspath := root.Path().Join(target) + abspath = os.NewHostPath(filepath.Clean(abspath.String())) + destdir := abspath.Dir() - if !strings.HasPrefix(destdir, root.HostDir()) { + if !destdir.HasPrefix(root.Path().String()) { return errors.E(ErrInvalidGenBlockLabel, "label path computes to %s which is not inside rootdir %s", - abspath, root.HostDir()) + abspath, root.Path()) } // We need to check that destdir, or any of its parents, is not a symlink or a stack. - for strings.HasPrefix(destdir, root.HostDir()) && destdir != root.HostDir() { - info, err := os.Lstat(destdir) + for destdir.HasPrefix(root.Path().String()) && destdir != root.Path() { + info, err := stdos.Lstat(destdir.String()) if err != nil { if errors.Is(err, fs.ErrNotExist) { - destdir = filepath.Dir(destdir) + destdir = destdir.Dir() continue } return errors.E( @@ -1111,10 +1112,10 @@ func validateRootGenerateBlock(root *config.Root, block hcl.GenFileBlock) error block.Range, "%s: generate_file.context=root generates inside a stack %s", target, - project.PrjAbsPath(root.HostDir(), destdir), + project.PrjAbsPath(root.Path(), destdir), ) } - destdir = filepath.Dir(destdir) + destdir = destdir.Dir() } return nil @@ -1175,7 +1176,7 @@ func checkFileConflict(generated []GenFile) map[string]error { func loadAsserts(root *config.Root, st *config.Stack, evalctx *eval.Context) ([]config.Assert, error) { logger := log.With(). Str("action", "generate.loadAsserts"). - Str("rootdir", root.HostDir()). + Stringer("rootdir", root.Path()). Str("stack", st.Dir.String()). Logger() @@ -1290,7 +1291,7 @@ func loadStackCodeCfgs( asserts = append(asserts, gen.Asserts()...) } - err = handleAsserts(root.HostDir(), st.HostDir(root), asserts) + err = handleAsserts(root.Path(), st.HostDir(root), asserts) if err != nil { return nil, err } @@ -1355,7 +1356,7 @@ func cleanupOrphaned(root *config.Root, target *config.Tree, report Report) Repo logger.Debug().Msg("listing orphaned generated files") - orphanedGenFiles, err := ListStackGenFiles(root, target.HostDir()) + orphanedGenFiles, err := ListStackGenFiles(root, target.Path()) if err != nil { report.CleanupErr = err return report @@ -1365,9 +1366,9 @@ func cleanupOrphaned(root *config.Root, target *config.Tree, report Report) Repo deleteFailures := map[project.Path]*errors.List{} for _, genfile := range orphanedGenFiles { - genfileAbspath := filepath.Join(target.HostDir(), genfile) - dir := project.PrjAbsPath(root.HostDir(), filepath.Dir(genfileAbspath)) - if err := os.Remove(genfileAbspath); err != nil { + genfileAbspath := target.Path().Join(genfile) + dir := project.PrjAbsPath(root.Path(), genfileAbspath.Dir()) + if err := stdos.Remove(genfileAbspath.String()); err != nil { if deleteFailures[dir] == nil { deleteFailures[dir] = errors.L() } diff --git a/generate/generate_hcl_test.go b/generate/generate_hcl_test.go index d1274aa33e..4cd9466b7e 100644 --- a/generate/generate_hcl_test.go +++ b/generate/generate_hcl_test.go @@ -3001,9 +3001,9 @@ func TestGenerateHCLCleanupOldFilesIgnoreSymlinks(t *testing.T) { ) targEntry := s.RootEntry().CreateDir("target") - linkPath := filepath.Join(stackEntry.Path(), "link") - test.MkdirAll(t, targEntry.Path()) - assert.NoError(t, os.Symlink(targEntry.Path(), linkPath)) + linkPath := stackEntry.Path().Join("link") + test.MkdirAll(t, targEntry.Path().String()) + assert.NoError(t, os.Symlink(targEntry.Path().String(), linkPath.String())) // Creates a file with a generated header inside the symlinked directory. // It should never return in the report. @@ -3028,8 +3028,8 @@ func TestGenerateHCLCleanupOldFilesIgnoreDotDirs(t *testing.T) { s := sandbox.NoGit(t, true) // Creates a file with a generated header inside dot dirs. - test.WriteFile(t, filepath.Join(s.RootDir(), ".terramate"), "test.tf", genhcl.DefaultHeader()) - test.WriteFile(t, filepath.Join(s.RootDir(), ".another"), "test.tf", genhcl.DefaultHeader()) + test.WriteFile(t, s.RootDir().Join(".terramate"), "test.tf", genhcl.DefaultHeader()) + test.WriteFile(t, s.RootDir().Join(".another"), "test.tf", genhcl.DefaultHeader()) assertEqualReports(t, s.Generate(), generate.Report{}) } @@ -3040,8 +3040,8 @@ func TestGenerateHCLCleanupOldFilesDONTIgnoreDotFiles(t *testing.T) { s := sandbox.NoGit(t, true) // Creates a file with a generated header inside dot dirs. - test.WriteFile(t, filepath.Join(s.RootDir(), "somedir"), ".test.tf", genhcl.DefaultHeader()) - test.WriteFile(t, filepath.Join(s.RootDir(), "another"), ".test.tf", genhcl.DefaultHeader()) + test.WriteFile(t, s.RootDir().Join("somedir"), ".test.tf", genhcl.DefaultHeader()) + test.WriteFile(t, s.RootDir().Join("another"), ".test.tf", genhcl.DefaultHeader()) assertEqualReports(t, s.Generate(), generate.Report{ Successes: []generate.Result{ @@ -3082,8 +3082,8 @@ func TestGenerateHCLTerramateRootMetadata(t *testing.T) { }) want := Doc( - Str("terramate_root_path_abs", escapeBackslash(s.RootDir())), - Str("terramate_root_path_basename", filepath.Base(s.RootDir())), + Str("terramate_root_path_abs", escapeBackslash(s.RootDir().String())), + Str("terramate_root_path_basename", filepath.Base(s.RootDir().String())), ).String() got := stackEntry.ReadFile(generatedFile) diff --git a/generate/generate_list_test.go b/generate/generate_list_test.go index 4f702acf75..fb1c56492d 100644 --- a/generate/generate_list_test.go +++ b/generate/generate_list_test.go @@ -5,7 +5,6 @@ package generate_test import ( "fmt" - "path/filepath" "strings" "testing" @@ -13,6 +12,7 @@ import ( "github.com/terramate-io/terramate/config" "github.com/terramate-io/terramate/generate" "github.com/terramate-io/terramate/generate/genhcl" + "github.com/terramate-io/terramate/os" . "github.com/terramate-io/terramate/test/hclwrite/hclutils" "github.com/terramate-io/terramate/test/sandbox" ) @@ -225,9 +225,9 @@ func TestGeneratedFilesListing(t *testing.T) { s := sandbox.NoGit(t, true) s.BuildTree(tcase.layout) - var listdir string + var listdir os.Path if tcase.dir != "" { - listdir = filepath.Join(s.RootDir(), tcase.dir) + listdir = s.RootDir().Join(tcase.dir) } else { listdir = s.RootDir() } diff --git a/generate/generate_root_file_test.go b/generate/generate_root_file_test.go index 56263e3c49..2495cde6e7 100644 --- a/generate/generate_root_file_test.go +++ b/generate/generate_root_file_test.go @@ -6,7 +6,6 @@ package generate_test import ( "fmt" "os" - "path/filepath" "testing" "github.com/terramate-io/terramate/errors" @@ -734,16 +733,16 @@ func TestGenerateFileWithRootContextRemoveFilesWhenConditionIsFalse(t *testing.T assertFileExist := func(file string) { t.Helper() - path := filepath.Join(s.RootDir(), file) - if _, err := os.Stat(path); err != nil { + path := s.RootDir().Join(file) + if _, err := os.Stat(path.String()); err != nil { t.Fatalf("want file %q to exist, instead got: %v", path, err) } } assertFileDontExist := func(file string) { t.Helper() - path := filepath.Join(s.RootDir(), file) - _, err := os.Stat(path) + path := s.RootDir().Join(file) + _, err := os.Stat(path.String()) if errors.Is(err, os.ErrNotExist) { return diff --git a/generate/generate_stack_file_test.go b/generate/generate_stack_file_test.go index 92a8fb455a..da55f00aba 100644 --- a/generate/generate_stack_file_test.go +++ b/generate/generate_stack_file_test.go @@ -6,7 +6,6 @@ package generate_test import ( "fmt" "os" - "path/filepath" "testing" "github.com/madlambda/spells/assert" @@ -725,16 +724,16 @@ func TestGenerateFileRemoveFilesWhenConditionIsFalse(t *testing.T) { assertFileExist := func(file string) { t.Helper() - path := filepath.Join(stackEntry.Path(), file) - if _, err := os.Stat(path); err != nil { + path := stackEntry.Path().Join(file) + if _, err := os.Stat(path.String()); err != nil { t.Fatalf("want file %q to exist, instead got: %v", path, err) } } assertFileDontExist := func(file string) { t.Helper() - path := filepath.Join(stackEntry.Path(), file) - _, err := os.Stat(path) + path := stackEntry.Path().Join(file) + _, err := os.Stat(path.String()) if errors.Is(err, os.ErrNotExist) { return @@ -810,7 +809,7 @@ func TestGenerateFileTerramateRootMetadata(t *testing.T) { }, }) - want := s.RootDir() + "-" + filepath.Base(s.RootDir()) + want := s.RootDir().String() + "-" + s.RootDir().Base() got := stackEntry.ReadFile(generatedFile) assert.EqualStrings(t, want, got) diff --git a/generate/generate_test.go b/generate/generate_test.go index 65870d207a..977e186c47 100644 --- a/generate/generate_test.go +++ b/generate/generate_test.go @@ -6,7 +6,7 @@ package generate_test import ( "fmt" "io/fs" - "os" + stdos "os" "path/filepath" "runtime" "strings" @@ -18,6 +18,7 @@ import ( "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/generate" "github.com/terramate-io/terramate/generate/genhcl" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" stackpkg "github.com/terramate-io/terramate/stack" "github.com/terramate-io/terramate/test" @@ -1081,18 +1082,18 @@ func TestTmGenDeletesFileWhenHidden(t *testing.T) { assertFileExist := func(file string) { t.Helper() - path := filepath.Join(stackEntry.Path(), file) - if _, err := os.Stat(path); err != nil { + path := stackEntry.Path().Join(file) + if _, err := stdos.Stat(path.String()); err != nil { t.Fatalf("want file %q to exist, instead got: %v", path, err) } } assertFileDontExist := func(file string) { t.Helper() - path := filepath.Join(stackEntry.Path(), file) - _, err := os.Stat(path) + path := stackEntry.Path().Join(file) + _, err := stdos.Stat(path.String()) - if errors.Is(err, os.ErrNotExist) { + if errors.Is(err, stdos.ErrNotExist) { return } @@ -1153,15 +1154,15 @@ func testCodeGeneration(t *testing.T, tcases []testcase) { s := sandbox.NoGit(t, true) s.BuildTree(tcase.layout) - configurationFiles := map[string]struct{}{} + configurationFiles := map[os.Path]struct{}{} for _, cfg := range tcase.configs { - path := filepath.Join(s.RootDir(), cfg.path) + path := s.RootDir().Join(cfg.path) filename := cfg.filename if filename == "" { filename = config.DefaultFilename } - configurationFiles[filepath.Join(path, filename)] = struct{}{} + configurationFiles[path.Join(filename)] = struct{}{} test.AppendFile(t, path, filename, cfg.add.String()) } @@ -1218,8 +1219,8 @@ func testCodeGeneration(t *testing.T, tcases []testcase) { } } - createdBySandbox := func(path string) bool { - relpath := strings.TrimPrefix(path, s.RootDir()) + createdBySandbox := func(path os.Path) bool { + relpath := path.TrimPrefix(s.RootDir()) // For windows compatibility, since builder strings // are unix like. relpath = filepath.ToSlash(relpath) @@ -1242,12 +1243,12 @@ func testCodeGeneration(t *testing.T, tcases []testcase) { return false } - createdByConfig := func(path string) bool { + createdByConfig := func(path os.Path) bool { _, ok := configurationFiles[path] return ok } - err := filepath.WalkDir(s.RootDir(), func(path string, d fs.DirEntry, err error) error { + err := filepath.WalkDir(s.RootDir().String(), func(pathstr string, d fs.DirEntry, err error) error { t.Helper() assert.NoError(t, err, "checking for unwanted generated files") @@ -1261,6 +1262,8 @@ func testCodeGeneration(t *testing.T, tcases []testcase) { return nil } + path := os.NewHostPath(pathstr) + if createdBySandbox(path) || createdByConfig(path) { return nil } diff --git a/generate/genhcl/genhcl.go b/generate/genhcl/genhcl.go index b657f243e1..3f966b7145 100644 --- a/generate/genhcl/genhcl.go +++ b/generate/genhcl/genhcl.go @@ -21,6 +21,7 @@ import ( "github.com/terramate-io/terramate/hcl/ast" "github.com/terramate-io/terramate/hcl/fmt" "github.com/terramate-io/terramate/hcl/info" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/stdlib" "github.com/terramate-io/terramate/hcl/eval" @@ -366,7 +367,7 @@ func Load( return hcls, nil } -func evalErr(rootdir string, kind errors.Kind, block hcl.GenHCLBlock, err error) error { +func evalErr(rootdir os.Path, kind errors.Kind, block hcl.GenHCLBlock, err error) error { if block.IsImplicitBlock { return errors.E(kind, err, `tmgen file "%s"`, project.PrjAbsPath(rootdir, block.Range.HostPath())) } diff --git a/generate/genhcl/genhcl_test.go b/generate/genhcl/genhcl_test.go index 010064998a..f31ab50d30 100644 --- a/generate/genhcl/genhcl_test.go +++ b/generate/genhcl/genhcl_test.go @@ -5,7 +5,6 @@ package genhcl_test import ( "fmt" - "path/filepath" "strings" "testing" @@ -1883,7 +1882,7 @@ func (tcase testcase) run(t *testing.T) { if filename == "" { filename = config.DefaultFilename } - path := filepath.Join(s.RootDir(), cfg.path) + path := s.RootDir().Join(cfg.path) test.AppendFile(t, path, filename, cfg.add.String()) } diff --git a/generate/genhcl/partial_eval_test.go b/generate/genhcl/partial_eval_test.go index 1e816aa432..939e621221 100644 --- a/generate/genhcl/partial_eval_test.go +++ b/generate/genhcl/partial_eval_test.go @@ -5,7 +5,6 @@ package genhcl_test import ( "fmt" - "path/filepath" "strings" "testing" @@ -1870,7 +1869,7 @@ EOT s := sandbox.NoGit(t, true) stackEntry := s.CreateStack(stackname) st := stackEntry.Load(s.Config()) - path := filepath.Join(s.RootDir(), stackname) + path := s.RootDir().Join(stackname) if tcase.globals == nil { tcase.globals = Globals() } diff --git a/git/git.go b/git/git.go index 7cabe7f98e..fc6f47974d 100644 --- a/git/git.go +++ b/git/git.go @@ -6,7 +6,7 @@ package git import ( "errors" "fmt" - "os" + stdos "os" "os/exec" "sort" "strconv" @@ -14,6 +14,7 @@ import ( "time" "github.com/rs/zerolog/log" + "github.com/terramate-io/terramate/os" ) type ( @@ -24,7 +25,7 @@ type ( ProgramPath string // WorkingDir sets the directory where the commands will be applied. - WorkingDir string + WorkingDir os.Path // Env is the environment variables to be passed over to git. // If it is nil it means no environment variables should be passed. @@ -150,19 +151,19 @@ func (git *Git) applyDefaults() error { } if cfg.WorkingDir == "" { - wd, err := os.Getwd() + wd, err := stdos.Getwd() if err != nil { return fmt.Errorf("failed to get working directory: %w", err) } - cfg.WorkingDir = wd + cfg.WorkingDir = os.NewHostPath(wd) } return nil } func (git *Git) validate() error { cfg := git.cfg() - _, err := os.Stat(cfg.ProgramPath) + _, err := stdos.Stat(cfg.ProgramPath) if err != nil { return fmt.Errorf("failed to stat git program path \"%s\": %w: %v", cfg.ProgramPath, ErrInvalidConfig, err) @@ -180,7 +181,7 @@ func (git *Git) Version() (string, error) { cfg := git.cfg() logger := log.With(). Str("action", "Version()"). - Str("workingDir", cfg.WorkingDir). + Stringer("workingDir", cfg.WorkingDir). Logger() logger.Debug().Msg("Get git version.") @@ -202,7 +203,7 @@ func (git *Git) Version() (string, error) { // repository", in other words, a repository not intended for work but just // store revisions. // Beware: Init is a porcelain method. -func (git *Git) Init(dir string, defaultBranch string, bare bool) error { +func (git *Git) Init(dir os.Path, defaultBranch string, bare bool) error { cfg := git.cfg() if !cfg.AllowPorcelain { return fmt.Errorf("Init: %w", ErrDenyPorcelain) @@ -217,7 +218,7 @@ func (git *Git) Init(dir string, defaultBranch string, bare bool) error { args = append(args, "--bare") } - args = append(args, dir) + args = append(args, dir.String()) _, err := git.exec("init", args...) if err != nil { return err @@ -359,7 +360,7 @@ func (git *Git) Add(files ...string) error { log.Debug(). Str("action", "Add()"). - Str("workingDir", cfg.WorkingDir). + Stringer("workingDir", cfg.WorkingDir). Msg("Add file to current staged index.") _, err := git.exec("add", files...) return err @@ -367,12 +368,12 @@ func (git *Git) Add(files ...string) error { // Clone will clone the given repo inside the given dir. // Beware: Clone is a porcelain method. -func (git *Git) Clone(repoURL, dir string) error { +func (git *Git) Clone(repoURL string, dir os.Path) error { cfg := git.cfg() if !cfg.AllowPorcelain { return fmt.Errorf("Clone: %w", ErrDenyPorcelain) } - _, err := git.exec("clone", repoURL, dir) + _, err := git.exec("clone", repoURL, dir.String()) return err } @@ -416,7 +417,7 @@ func (git *Git) FetchRemoteRev(remote, ref string) (Ref, error) { cfg := git.cfg() logger := log.With(). Str("action", "FetchRemoteRev()"). - Str("workingDir", cfg.WorkingDir). + Stringer("workingDir", cfg.WorkingDir). Logger() logger.Debug().Msg("List references in remote repository.") @@ -502,7 +503,7 @@ func (git *Git) NewBranch(name string) error { log.Debug(). Str("action", "NewBranch()"). - Str("workingDir", git.cfg().WorkingDir). + Stringer("workingDir", git.cfg().WorkingDir). Str("reference", name). Msg("Create new branch.") _, err = git.exec("update-ref", "refs/heads/"+name, "HEAD") @@ -518,7 +519,7 @@ func (git *Git) DeleteBranch(name string) error { log.Debug(). Str("action", "DeleteBranch()"). - Str("workingDir", git.cfg().WorkingDir). + Stringer("workingDir", git.cfg().WorkingDir). Str("reference", name). Msg("Delete branch.") _, err = git.exec("update-ref", "-d", "refs/heads/"+name) @@ -543,7 +544,7 @@ func (git *Git) Checkout(rev string, create bool) error { log.Debug(). Str("action", "Checkout()"). - Str("workingDir", git.cfg().WorkingDir). + Stringer("workingDir", git.cfg().WorkingDir). Str("reference", rev). Msg("Checkout.") _, err := git.exec("checkout", rev) @@ -559,7 +560,7 @@ func (git *Git) Merge(branch string) error { log.Debug(). Str("action", "Merge()"). - Str("workingDir", git.cfg().WorkingDir). + Stringer("workingDir", git.cfg().WorkingDir). Str("reference", branch). Msg("Merge.") _, err := git.exec("merge", "--no-ff", branch) @@ -597,7 +598,7 @@ func (git *Git) ListUntracked(dirs ...string) ([]string, error) { log.Debug(). Str("action", "ListUntracked()"). - Str("workingDir", git.cfg().WorkingDir). + Stringer("workingDir", git.cfg().WorkingDir). Msg("List untracked files.") out, err := git.exec("ls-files", args...) if err != nil { @@ -621,7 +622,7 @@ func (git *Git) ListUncommitted(dirs ...string) ([]string, error) { log.Debug(). Str("action", "ListUncommitted()"). - Str("workingDir", git.cfg().WorkingDir). + Stringer("workingDir", git.cfg().WorkingDir). Msg("List uncommitted files.") out, err := git.exec("ls-files", args...) if err != nil { @@ -742,7 +743,7 @@ func (git *Git) exec(command string, args ...string) (string, error) { cmd := exec.Cmd{ Path: cfg.ProgramPath, Args: []string{cfg.ProgramPath}, - Dir: cfg.WorkingDir, + Dir: cfg.WorkingDir.String(), Env: []string{}, } diff --git a/git/git_test.go b/git/git_test.go index b80226e1ad..c0355adcdd 100644 --- a/git/git_test.go +++ b/git/git_test.go @@ -16,6 +16,7 @@ import ( "github.com/madlambda/spells/assert" "github.com/rs/zerolog" "github.com/terramate-io/terramate/git" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/test" "github.com/terramate-io/terramate/test/sandbox" ) @@ -36,7 +37,7 @@ func TestGit(t *testing.T) { func TestGitLog(t *testing.T) { t.Parallel() type testcase struct { - repo func(t *testing.T) string + repo func(t *testing.T) os.Path revs []string want []git.LogLine wantErr error @@ -145,10 +146,10 @@ func TestGitOptions(t *testing.T) { git := test.NewGitWrapper(t, repodir1, []string{}) gotRepoDir1, err := git.Root() assert.NoError(t, err, "root failed") - assert.EqualStrings(t, repodir1, gotRepoDir1) + assert.EqualStrings(t, repodir1.String(), gotRepoDir1) gotRepoDir2, err := git.With().WorkingDir(repodir2).Wrapper().Root() assert.NoError(t, err) - assert.EqualStrings(t, repodir2, gotRepoDir2) + assert.EqualStrings(t, repodir2.String(), gotRepoDir2) } func TestClone(t *testing.T) { @@ -162,11 +163,11 @@ func TestClone(t *testing.T) { git.CommitAll("add file") - repoURL := "file://" + s.RootDir() + repoURL := string("file://" + s.RootDir()) cloneDir := test.TempDir(t) git.Clone(repoURL, cloneDir) - got := test.ReadFile(t, cloneDir, filename) + got := test.ReadFile(t, cloneDir.String(), filename) assert.EqualStrings(t, content, string(got)) } @@ -293,7 +294,7 @@ func TestListingAvailableRemotes(t *testing.T) { remote := gitRemote.Name remoteDir := test.EmptyRepo(t, true) - err := g.RemoteAdd(remote, remoteDir) + err := g.RemoteAdd(remote, remoteDir.String()) assert.NoError(t, err) for _, branch := range gitRemote.Branches { @@ -330,7 +331,7 @@ func TestListRemoteWithMultipleBranches(t *testing.T) { remoteDir := test.EmptyRepo(t, true) - assert.NoError(t, g.RemoteAdd(remote, remoteDir)) + assert.NoError(t, g.RemoteAdd(remote, remoteDir.String())) assert.NoError(t, g.Push(remote, defaultBranch)) branches := []string{"b1", "b2", "b3"} @@ -396,7 +397,7 @@ func TestShowMetadata(t *testing.T) { gw := test.NewGitWrapper(t, repodir, env) filename := test.WriteFile(t, repodir, "README.md", "# Test") - assert.NoError(t, gw.Add(filename), "git add %s", filename) + assert.NoError(t, gw.Add(filename.String()), "git add %s", filename) commitMsg := tc.title if tc.description != "" { @@ -448,7 +449,7 @@ func TestGetConfigValue(t *testing.T) { tests := map[string]string{ "user.name": test.Username, "user.email": test.Email, - "safe.directory": repodir, + "safe.directory": repodir.String(), } for k, v := range tests { @@ -464,11 +465,13 @@ func TestGetConfigValue(t *testing.T) { const defaultBranch = "main" -func mkOneCommitRepo(t *testing.T) string { +func mkOneCommitRepo(t *testing.T) os.Path { dir := test.EmptyRepo(t, false) - repodir, err := filepath.EvalSymlinks(dir) + repostr, err := filepath.EvalSymlinks(dir.String()) assert.NoError(t, err) + repodir := os.NewHostPath(repostr) + // Fixing all the information used to create the SHA-1 below: // CommitID: a022c39b57b1e711fb9298a05aacc699773e6d36 @@ -485,7 +488,7 @@ func mkOneCommitRepo(t *testing.T) string { gw := test.NewGitWrapper(t, repodir, env) filename := test.WriteFile(t, repodir, "README.md", "# Test") - assert.NoError(t, gw.Add(filename), "git add %s", filename) + assert.NoError(t, gw.Add(filename.String()), "git add %s", filename) err = gw.Commit("some message") assert.NoError(t, err, "commit") @@ -501,7 +504,7 @@ func addDefaultRemoteRev(t *testing.T, git *git.Git) (string, string) { t.Helper() remoteDir := test.EmptyRepo(t, true) - err := git.RemoteAdd(remote, remoteDir) + err := git.RemoteAdd(remote, remoteDir.String()) assert.NoError(t, err) err = git.Push(remote, revision) diff --git a/git/options.go b/git/options.go index 9248a4078b..69a97b9157 100644 --- a/git/options.go +++ b/git/options.go @@ -3,6 +3,8 @@ package git +import "github.com/terramate-io/terramate/os" + // Options is a customizable options object to be used with the [Git] // wrapper. type Options struct { @@ -10,7 +12,7 @@ type Options struct { } // WorkingDir sets the wrapper working directory. -func (opt *Options) WorkingDir(wd string) *Options { +func (opt *Options) WorkingDir(wd os.Path) *Options { opt.config.WorkingDir = wd return opt } diff --git a/git/url_test.go b/git/url_test.go index 0b99f43643..fd1551d66f 100644 --- a/git/url_test.go +++ b/git/url_test.go @@ -160,9 +160,9 @@ func TestNormalizeGitURL(t *testing.T) { }, { name: "filesystem path returns as local", - raw: tempDir, + raw: tempDir.String(), normalized: git.Repository{ - RawURL: tempDir, + RawURL: tempDir.String(), Host: "local", }, }, diff --git a/globals/globals_test.go b/globals/globals_test.go index c380d27c25..7dd997b37a 100644 --- a/globals/globals_test.go +++ b/globals/globals_test.go @@ -4,7 +4,6 @@ package globals_test import ( - "path/filepath" "testing" "github.com/madlambda/spells/assert" @@ -4062,7 +4061,7 @@ func TestLoadGlobalsErrors(t *testing.T) { s.BuildTree(tcase.layout) for _, c := range tcase.configs { - path := filepath.Join(s.RootDir(), c.path) + path := s.RootDir().Join(c.path) test.AppendFile(t, path, config.DefaultFilename, c.body) } @@ -4092,7 +4091,7 @@ func testGlobals(t *testing.T, tcase testcase) { s := sandbox.NoGit(t, true) s.BuildTree(tcase.layout) for _, globalBlock := range tcase.configs { - path := filepath.Join(s.RootDir(), globalBlock.path) + path := s.RootDir().Join(globalBlock.path) filename := config.DefaultFilename if globalBlock.filename != "" { filename = globalBlock.filename diff --git a/hcl/ast/attribute.go b/hcl/ast/attribute.go index fa11d42646..d57461c626 100644 --- a/hcl/ast/attribute.go +++ b/hcl/ast/attribute.go @@ -9,6 +9,7 @@ import ( hhcl "github.com/terramate-io/hcl/v2" "github.com/terramate-io/hcl/v2/hclsyntax" "github.com/terramate-io/terramate/hcl/info" + "github.com/terramate-io/terramate/os" ) // Attribute represents a parsed attribute. @@ -22,7 +23,7 @@ type Attributes map[string]Attribute // NewAttribute creates a new attribute given a parsed attribute and the rootdir // of the project. -func NewAttribute(rootdir string, val *hhcl.Attribute) Attribute { +func NewAttribute(rootdir os.Path, val *hhcl.Attribute) Attribute { return Attribute{ Range: info.NewRange(rootdir, val.Range), Attribute: val, @@ -40,7 +41,7 @@ func (a Attributes) SortedList() AttributeSlice { } // NewAttributes creates a map of Attributes from the raw hcl.Attributes. -func NewAttributes(rootdir string, rawAttrs hhcl.Attributes) Attributes { +func NewAttributes(rootdir os.Path, rawAttrs hhcl.Attributes) Attributes { attrs := make(Attributes) for _, rawAttr := range rawAttrs { attrs[rawAttr.Name] = NewAttribute(rootdir, rawAttr) diff --git a/hcl/ast/block.go b/hcl/ast/block.go index 817592d224..38a4ba4fe2 100644 --- a/hcl/ast/block.go +++ b/hcl/ast/block.go @@ -7,6 +7,7 @@ import ( "github.com/terramate-io/hcl/v2" "github.com/terramate-io/hcl/v2/hclsyntax" "github.com/terramate-io/terramate/hcl/info" + "github.com/terramate-io/terramate/os" ) // Block is a wrapper to the hclsyntax.Block but with the file origin. @@ -23,7 +24,7 @@ type Block struct { type Blocks []*Block // NewBlock creates a new block wrapper. -func NewBlock(rootdir string, block *hclsyntax.Block) *Block { +func NewBlock(rootdir os.Path, block *hclsyntax.Block) *Block { attrs := make(Attributes) for name, val := range block.Body.Attributes { attrs[name] = NewAttribute(rootdir, val.AsHCLAttribute()) @@ -41,7 +42,7 @@ func NewBlock(rootdir string, block *hclsyntax.Block) *Block { } // NewBlocks creates a Block slice from the raw hclsyntax.Block. -func NewBlocks(rootdir string, rawblocks hclsyntax.Blocks) Blocks { +func NewBlocks(rootdir os.Path, rawblocks hclsyntax.Blocks) Blocks { var blocks Blocks for _, rawblock := range rawblocks { blocks = append(blocks, NewBlock(rootdir, rawblock)) @@ -56,7 +57,7 @@ func (b *Block) LabelRanges() hcl.Range { switch n := len(b.Block.LabelRanges); n { case 0: return hcl.Range{ - Filename: b.Range.HostPath(), + Filename: b.Range.HostPath().String(), // returns the range for the caret symbol below: // blockname { ... } diff --git a/hcl/ast/merged_block.go b/hcl/ast/merged_block.go index b267c07a6f..4e1525a79a 100644 --- a/hcl/ast/merged_block.go +++ b/hcl/ast/merged_block.go @@ -4,10 +4,10 @@ package ast import ( - "path/filepath" "sort" "github.com/terramate-io/terramate/errors" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" ) @@ -208,8 +208,8 @@ func (mergedBlocks MergedLabelBlocks) AsList() []*MergedBlock { return allblocks } -func sameDir(file1, file2 string) bool { - return filepath.Dir(file1) == filepath.Dir(file2) +func sameDir(file1, file2 os.Path) bool { + return file1.Dir() == file2.Dir() } func newLabels(labels []string) [project.MaxGlobalLabels]string { diff --git a/hcl/eval/eval_test.go b/hcl/eval/eval_test.go index 64df6fd956..8a82edd9dd 100644 --- a/hcl/eval/eval_test.go +++ b/hcl/eval/eval_test.go @@ -12,6 +12,7 @@ import ( "github.com/terramate-io/hcl/v2/hclsyntax" "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/hcl/eval" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/stdlib" "github.com/terramate-io/terramate/test" errtest "github.com/terramate-io/terramate/test/errors" @@ -24,7 +25,7 @@ type want struct { } type testcase struct { name string - basedir string + basedir os.Path expr string want want } @@ -86,7 +87,7 @@ func TestEvalTmFuncall(t *testing.T) { cfg := fmt.Sprintf("%s = %s", attrname, strings.ReplaceAll(tc.expr, `\`, `\\`)) fname := test.WriteFile(t, test.TempDir(t), "test-tm_ternary.hcl", cfg) parser := hclparse.NewParser() - file, diags := parser.ParseHCL([]byte(cfg), fname) + file, diags := parser.ParseHCL([]byte(cfg), fname.String()) if diags.HasErrors() { t.Fatalf("expr %q is not valid", tc.expr) } diff --git a/hcl/eval/eval_unix_test.go b/hcl/eval/eval_unix_test.go index 7d8e975dc3..52ead876f5 100644 --- a/hcl/eval/eval_unix_test.go +++ b/hcl/eval/eval_unix_test.go @@ -8,6 +8,7 @@ package eval_test import ( "testing" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/test" "github.com/zclconf/go-cty/cty" ) @@ -49,7 +50,7 @@ func tmAbspathTestcases(t *testing.T) []testcase { expr: `tm_abspath("")`, basedir: tempDir, want: want{ - value: cty.StringVal(tempDir), + value: cty.StringVal(tempDir.String()), }, }, { @@ -62,4 +63,4 @@ func tmAbspathTestcases(t *testing.T) []testcase { } } -func root(_ *testing.T) string { return "/" } +func root(_ *testing.T) os.Path { return os.NewHostPath("/") } diff --git a/hcl/eval/partial_eval_bench_test.go b/hcl/eval/partial_eval_bench_test.go index 7c8a8e9b74..d14629cafd 100644 --- a/hcl/eval/partial_eval_bench_test.go +++ b/hcl/eval/partial_eval_bench_test.go @@ -4,7 +4,6 @@ package eval_test import ( - "os" "strings" "testing" @@ -12,11 +11,12 @@ import ( "github.com/terramate-io/hcl/v2/hclsyntax" "github.com/terramate-io/terramate/hcl/eval" "github.com/terramate-io/terramate/stdlib" + "github.com/terramate-io/terramate/test" "github.com/zclconf/go-cty/cty" ) -func setupContext() *eval.Context { - ctx := eval.NewContext(stdlib.Functions(os.TempDir(), []string{})) +func setupContext(t testing.TB) *eval.Context { + ctx := eval.NewContext(stdlib.Functions(test.TempDir(t), []string{})) ctx.SetNamespace("global", map[string]cty.Value{ "true": cty.True, "false": cty.False, @@ -43,7 +43,7 @@ func setupContext() *eval.Context { func BenchmarkPartialEvalComplex(b *testing.B) { b.StopTimer() - ctx := setupContext() + ctx := setupContext(b) exprBytes := []byte(`[ { @@ -108,7 +108,7 @@ func BenchmarkPartialEvalComplex(b *testing.B) { func BenchmarkPartialEvalSmallString(b *testing.B) { b.StopTimer() - ctx := setupContext() + ctx := setupContext(b) exprBytes := []byte(`"terramate is fun"`) @@ -127,7 +127,7 @@ func BenchmarkPartialEvalSmallString(b *testing.B) { func BenchmarkPartialEvalHugeString(b *testing.B) { b.StopTimer() - ctx := setupContext() + ctx := setupContext(b) exprBytes := []byte(`"` + strings.Repeat(`terramate is fun\n`, 1000) + `"`) @@ -146,7 +146,7 @@ func BenchmarkPartialEvalHugeString(b *testing.B) { func BenchmarkPartialEvalHugeInterpolatedString(b *testing.B) { b.StopTimer() - ctx := setupContext() + ctx := setupContext(b) exprBytes := []byte(`"` + strings.Repeat(`${global.string} is fun\n`, 1000) + `"`) @@ -165,7 +165,7 @@ func BenchmarkPartialEvalHugeInterpolatedString(b *testing.B) { func BenchmarkPartialEvalObject(b *testing.B) { b.StopTimer() - ctx := setupContext() + ctx := setupContext(b) exprBytes := []byte(`{ a = 1 diff --git a/hcl/eval/partial_eval_test.go b/hcl/eval/partial_eval_test.go index 08cebb4a70..b91e391577 100644 --- a/hcl/eval/partial_eval_test.go +++ b/hcl/eval/partial_eval_test.go @@ -4,7 +4,6 @@ package eval_test import ( - "os" "testing" "github.com/google/go-cmp/cmp" @@ -15,6 +14,7 @@ import ( "github.com/terramate-io/terramate/hcl/ast" "github.com/terramate-io/terramate/hcl/eval" "github.com/terramate-io/terramate/stdlib" + "github.com/terramate-io/terramate/test" errtest "github.com/terramate-io/terramate/test/errors" "github.com/zclconf/go-cty/cty" ) @@ -485,7 +485,7 @@ EOT tc := tc t.Run(tc.expr, func(t *testing.T) { t.Parallel() - ctx := eval.NewContext(stdlib.Functions(os.TempDir(), []string{})) + ctx := eval.NewContext(stdlib.Functions(test.TempDir(t), []string{})) ctx.SetNamespace("global", map[string]cty.Value{ "number": cty.NumberIntVal(10), "string": cty.StringVal("terramate"), diff --git a/hcl/fmt/fmt.go b/hcl/fmt/fmt.go index b40a11f929..88ac224f61 100644 --- a/hcl/fmt/fmt.go +++ b/hcl/fmt/fmt.go @@ -6,8 +6,8 @@ package fmt import ( "fmt" - "os" - "path/filepath" + stdos "os" + "slices" "sort" "github.com/terramate-io/hcl/v2" @@ -15,6 +15,7 @@ import ( "github.com/terramate-io/hcl/v2/hclwrite" "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/fs" + "github.com/terramate-io/terramate/os" ) // ErrHCLSyntax is the error kind for syntax errors. @@ -25,7 +26,7 @@ const ErrReadFile errors.Kind = "failed to read file" // FormatResult represents the result of a formatting operation. type FormatResult struct { - path string + path os.Path formatted string } @@ -34,8 +35,8 @@ type FormatResult struct { // element on the list resides on its own line followed by a comma. // // It returns an error if the given source is invalid HCL. -func FormatMultiline(src, filename string) (string, error) { - parsed, diags := hclwrite.ParseConfig([]byte(src), filename, hcl.InitialPos) +func FormatMultiline(src string, filename os.Path) (string, error) { + parsed, diags := hclwrite.ParseConfig([]byte(src), filename.String(), hcl.InitialPos) if diags.HasErrors() { return "", errors.E(ErrHCLSyntax, diags) } @@ -45,8 +46,8 @@ func FormatMultiline(src, filename string) (string, error) { // Format will format the given source code using hcl.Format. // It returns an error if the given source is invalid HCL. -func Format(src, filename string) (string, error) { - parsed, diags := hclwrite.ParseConfig([]byte(src), filename, hcl.InitialPos) +func Format(src string, filename os.Path) (string, error) { + parsed, diags := hclwrite.ParseConfig([]byte(src), filename.String(), hcl.InitialPos) if diags.HasErrors() { return "", errors.E(ErrHCLSyntax, diags) } @@ -64,12 +65,12 @@ func Format(src, filename string) (string, error) { // // All files will be left untouched. To save the formatted result on disk you // can use FormatResult.Save for each FormatResult. -func FormatTree(dir string) ([]FormatResult, error) { +func FormatTree(dir os.Path) ([]FormatResult, error) { files, err := fs.ListTerramateFiles(dir) if err != nil { return nil, errors.E(errFormatTree, err) } - sort.Strings(files) + slices.Sort(files) errs := errors.L() results, err := FormatFiles(dir, files) @@ -85,7 +86,7 @@ func FormatTree(dir string) ([]FormatResult, error) { sort.Strings(dirs) for _, d := range dirs { - subres, err := FormatTree(filepath.Join(dir, d)) + subres, err := FormatTree(dir.Join(d)) if err != nil { errs.Append(err) continue @@ -113,16 +114,12 @@ func FormatTree(dir string) ([]FormatResult, error) { // // All files will be left untouched. To save the formatted result on disk you // can use FormatResult.Save for each FormatResult. -func FormatFiles(basedir string, files []string) ([]FormatResult, error) { +func FormatFiles(basedir os.Path, files os.Paths) ([]FormatResult, error) { results := []FormatResult{} errs := errors.L() - for _, file := range files { - fname := file - if !filepath.IsAbs(file) { - fname = filepath.Join(basedir, file) - } - fileContents, err := os.ReadFile(fname) + for _, fname := range files { + fileContents, err := stdos.ReadFile(fname.String()) if err != nil { errs.Append(errors.E(ErrReadFile, err)) continue @@ -150,11 +147,11 @@ func FormatFiles(basedir string, files []string) ([]FormatResult, error) { // Save will save the formatted result on the original file, replacing // its original contents. func (f FormatResult) Save() error { - return os.WriteFile(f.path, []byte(f.formatted), 0644) + return stdos.WriteFile(f.path.String(), []byte(f.formatted), 0644) } // Path is the absolute path of the original file. -func (f FormatResult) Path() string { +func (f FormatResult) Path() os.Path { return f.path } diff --git a/hcl/fmt/fmt_test.go b/hcl/fmt/fmt_test.go index 0f668427be..5be48cd52f 100644 --- a/hcl/fmt/fmt_test.go +++ b/hcl/fmt/fmt_test.go @@ -4,8 +4,7 @@ package fmt_test import ( - "os" - "path/filepath" + stdos "os" "testing" "github.com/google/go-cmp/cmp" @@ -13,6 +12,7 @@ import ( "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/hcl" "github.com/terramate-io/terramate/hcl/fmt" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/test" . "github.com/terramate-io/terramate/test/hclutils" @@ -1251,7 +1251,7 @@ var = [ t.Parallel() tempdir := test.TempDir(t) - got, err := fmt.FormatMultiline(tcase.input, filepath.Join(tempdir, filename)) + got, err := fmt.FormatMultiline(tcase.input, tempdir.Join(filename)) FixupFiledirOnErrorsFileRanges(tempdir, tcase.wantErrs) errtest.AssertErrorList(t, err, tcase.wantErrs) @@ -1353,14 +1353,14 @@ d = [] continue } - checkResults := func(t *testing.T, res []fmt.FormatResult, wantFiles []string, tcase testcase, gotErr error) { + checkResults := func(t *testing.T, res []fmt.FormatResult, wantFiles []os.Path, tcase testcase, gotErr error) { wantErrs := []error{} for _, path := range wantFiles { for _, wantErr := range tcase.wantErrs { if e, ok := wantErr.(*errors.Error); ok { err := *e - err.FileRange.Filename = path + err.FileRange.Filename = path.String() wantErrs = append(wantErrs, &err) continue } @@ -1381,11 +1381,11 @@ d = [] } for i, wantFile := range wantFiles { - assert.EqualStrings(t, wantFile, res[i].Path()) + assert.EqualStrings(t, wantFile.String(), res[i].Path().String()) } } - saveFiles := func(t *testing.T, rootdir string, res []fmt.FormatResult) { + saveFiles := func(t *testing.T, rootdir os.Path, res []fmt.FormatResult) { for _, r := range res { assert.NoError(t, r.Save()) assertFileContains(t, r.Path(), r.Formatted()) @@ -1399,7 +1399,7 @@ d = [] } } - sandbox := func(t *testing.T) (string, []string) { + sandbox := func(t *testing.T) (os.Path, []os.Path) { const ( filename = "file.tm" subdirName = "subdir" @@ -1407,11 +1407,11 @@ d = [] rootdir := test.TempDir(t) test.Mkdir(t, rootdir, subdirName) - subdir := filepath.Join(rootdir, subdirName) + subdir := rootdir.Join(subdirName) wantFilepath := test.WriteFile(t, rootdir, filename, tcase.input) wantSubdirFilepath := test.WriteFile(t, subdir, filename, tcase.input) - return rootdir, []string{wantFilepath, wantSubdirFilepath} + return rootdir, []os.Path{wantFilepath, wantSubdirFilepath} } // piggyback on the overall formatting scenarios to check @@ -1450,8 +1450,8 @@ func TestFormatTreeFailsOnNonAccessibleSubdir(t *testing.T) { tmpdir := test.TempDir(t) test.Mkdir(t, tmpdir, subdir) - test.AssertChmod(t, filepath.Join(tmpdir, subdir), 0) - defer test.AssertChmod(t, filepath.Join(tmpdir, subdir), 0755) + test.AssertChmod(t, tmpdir.Join(subdir), 0) + defer test.AssertChmod(t, tmpdir.Join(subdir), 0755) _, err := fmt.FormatTree(tmpdir) assert.Error(t, err) @@ -1466,8 +1466,8 @@ func TestFormatTreeFailsOnNonAccessibleFile(t *testing.T) { b = 3 }`) - test.AssertChmod(t, filepath.Join(tmpdir, filename), 0) - defer test.AssertChmod(t, filepath.Join(tmpdir, filename), 0755) + test.AssertChmod(t, tmpdir.Join(filename), 0) + defer test.AssertChmod(t, tmpdir.Join(filename), 0755) _, err := fmt.FormatTree(tmpdir) assert.Error(t, err) @@ -1475,7 +1475,7 @@ func TestFormatTreeFailsOnNonAccessibleFile(t *testing.T) { func TestFormatTreeFailsOnNonExistentDir(t *testing.T) { tmpdir := test.TempDir(t) - _, err := fmt.FormatTree(filepath.Join(tmpdir, "non-existent")) + _, err := fmt.FormatTree(tmpdir.Join("non-existent")) assert.Error(t, err) } @@ -1496,7 +1496,7 @@ a = 1 test.WriteFile(t, tmpdir, "file.hcl", unformattedCode) test.Mkdir(t, tmpdir, subdirName) - subdir := filepath.Join(tmpdir, subdirName) + subdir := tmpdir.Join(subdirName) test.WriteFile(t, subdir, ".file.tm", unformattedCode) test.WriteFile(t, subdir, "file.tm", unformattedCode) test.WriteFile(t, subdir, "file.tm.hcl", unformattedCode) @@ -1506,12 +1506,12 @@ a = 1 assert.EqualInts(t, 0, len(got), "want no results, got: %v", got) } -func assertFileContains(t *testing.T, filepath, got string) { +func assertFileContains(t *testing.T, file os.Path, got string) { t.Helper() - data, err := os.ReadFile(filepath) + data, err := stdos.ReadFile(file.String()) assert.NoError(t, err, "reading file") want := string(data) - assert.EqualStrings(t, want, got, "file %q contents don't match", filepath) + assert.EqualStrings(t, want, got, "file %q contents don't match", file) } diff --git a/hcl/hcl.go b/hcl/hcl.go index b79a060250..81503c1dbb 100644 --- a/hcl/hcl.go +++ b/hcl/hcl.go @@ -5,10 +5,9 @@ package hcl import ( "fmt" - "os" + stdos "os" "path" "path/filepath" - "sort" "strings" "github.com/gobwas/glob" @@ -21,6 +20,7 @@ import ( "github.com/terramate-io/terramate/hcl/ast" "github.com/terramate-io/terramate/hcl/eval" "github.com/terramate-io/terramate/hcl/info" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/safeguard" "github.com/terramate-io/terramate/stdlib" @@ -85,7 +85,7 @@ type Config struct { Imported RawConfig // absdir is the absolute path to the configuration directory. - absdir string + absdir os.Path } // GenerateConfig includes code generation related configurations, like @@ -406,14 +406,14 @@ type TerramateParser struct { Experiments []string Imported RawConfig - rootdir string - dir string - files map[string][]byte // path=content + rootdir os.Path + dir os.Path + files map[os.Path][]byte // path=content hclparser *hclparse.Parser evalctx *eval.Context // parsedFiles stores a map of all parsed files - parsedFiles map[string]parsedFile + parsedFiles map[os.Path]parsedFile strict bool // if true, calling Parse() or MinimalParse() will fail. @@ -440,7 +440,7 @@ func NewRunConfig() *RunConfig { // by this parser or by another parser instance, respectively. type parsedFile struct { kind parsedKind - origin string + origin os.Path } type parsedKind int @@ -456,12 +456,12 @@ const ( // The parser creates sub-parsers for parsing imports but keeps a list of all // parsed files of all sub-parsers for detecting cycles and import duplications. // Calling Parse() or MinimalParse() multiple times is an error. -func NewTerramateParser(rootdir string, dir string, experiments ...string) (*TerramateParser, error) { - st, err := os.Stat(dir) +func NewTerramateParser(rootdir, dir os.Path, experiments ...string) (*TerramateParser, error) { + st, err := stdos.Stat(dir.String()) if err != nil { return nil, errors.E(err, "failed to stat directory %q", dir) } - if !strings.HasPrefix(dir, rootdir) { + if !dir.HasPrefix(rootdir.String()) { return nil, errors.E("directory %q is not inside root %q", dir, rootdir) } @@ -475,16 +475,16 @@ func NewTerramateParser(rootdir string, dir string, experiments ...string) (*Ter Experiments: experiments, rootdir: rootdir, dir: dir, - files: map[string][]byte{}, + files: map[os.Path][]byte{}, hclparser: hclparse.NewParser(), - parsedFiles: make(map[string]parsedFile), + parsedFiles: make(map[os.Path]parsedFile), evalctx: eval.NewContext(stdlib.Functions(dir, experiments)), }, nil } // NewStrictTerramateParser is like NewTerramateParser but will fail instead of // warn for harmless configuration mistakes. -func NewStrictTerramateParser(rootdir string, dir string, experiments ...string) (*TerramateParser, error) { +func NewStrictTerramateParser(rootdir os.Path, dir os.Path, experiments ...string) (*TerramateParser, error) { parser, err := NewTerramateParser(rootdir, dir, experiments...) if err != nil { return nil, err @@ -493,7 +493,7 @@ func NewStrictTerramateParser(rootdir string, dir string, experiments ...string) return parser, nil } -func (p *TerramateParser) addParsedFile(origin string, kind parsedKind, files ...string) { +func (p *TerramateParser) addParsedFile(origin os.Path, kind parsedKind, files ...os.Path) { for _, file := range files { p.parsedFiles[file] = parsedFile{ kind: kind, @@ -504,16 +504,15 @@ func (p *TerramateParser) addParsedFile(origin string, kind parsedKind, files .. // AddDir walks over all the files in the directory dir and add all .tm and // .tm.hcl files to the parser. -func (p *TerramateParser) AddDir(dir string) error { +func (p *TerramateParser) AddDir(dir os.Path) error { tmFiles, err := fs.ListTerramateFiles(dir) if err != nil { return errors.E(err, "adding directory to terramate parser") } for _, filename := range tmFiles { - path := filepath.Join(dir, filename) - - data, err := os.ReadFile(path) + path := dir.Join(filename.String()) + data, err := stdos.ReadFile(path.String()) if err != nil { return errors.E(err, "reading config file %q", path) } @@ -527,11 +526,11 @@ func (p *TerramateParser) AddDir(dir string) error { } // AddFile adds a file path to be parsed. -func (p *TerramateParser) AddFile(path string) error { - if !strings.HasPrefix(path, p.dir) { +func (p *TerramateParser) AddFile(path os.Path) error { + if !path.HasPrefix(p.dir.String()) { return errors.E("parser only allow files from directory %q", p.dir) } - data, err := os.ReadFile(path) + data, err := stdos.ReadFile(path.String()) if err != nil { return errors.E("adding file %q to parser", path, err) } @@ -539,15 +538,15 @@ func (p *TerramateParser) AddFile(path string) error { } // AddFileContent adds a file to the set of files to be parsed. -func (p *TerramateParser) AddFileContent(name string, data []byte) error { - if !strings.HasPrefix(name, p.dir) { +func (p *TerramateParser) AddFileContent(path os.Path, data []byte) error { + if !path.HasPrefix(p.dir.String()) { return errors.E("parser only allow files from directory %q", p.dir) } - if _, ok := p.files[name]; ok { - return errors.E(os.ErrExist, "adding file %q to the parser", name) + if _, ok := p.files[path]; ok { + return errors.E(stdos.ErrExist, "adding file %q to the parser", path) } - p.files[name] = data + p.files[path] = data return nil } @@ -588,11 +587,11 @@ func (p *TerramateParser) Parse() error { } // ParsedBodies returns a map of filename to the parsed hclsyntax.Body. -func (p *TerramateParser) ParsedBodies() map[string]*hclsyntax.Body { - parsed := make(map[string]*hclsyntax.Body) +func (p *TerramateParser) ParsedBodies() map[os.Path]*hclsyntax.Body { + parsed := make(map[os.Path]*hclsyntax.Body) bodyMap := p.hclparser.Files() for _, filename := range p.internalParsedFiles() { - hclfile := bodyMap[filename] + hclfile := bodyMap[filename.String()] // A cast error here would be a severe programming error on Terramate // side, so we are by design allowing the cast to panic parsed[filename] = hclfile.Body.(*hclsyntax.Body) @@ -643,7 +642,7 @@ func (p *TerramateParser) parseSyntax() error { errs := errors.L() for _, name := range p.sortedFilenames() { data := p.files[name] - _, diags := p.hclparser.ParseHCL(data, name) + _, diags := p.hclparser.ParseHCL(data, name.String()) if diags.HasErrors() { errs.Append(errors.E(ErrHCLSyntax, diags)) continue @@ -682,17 +681,17 @@ func (p *TerramateParser) handleImport(importBlock *ast.Block) error { srcBase := path.Base(src) srcDir := path.Dir(src) if path.IsAbs(srcDir) { // project-path - srcDir = filepath.Join(p.rootdir, srcDir) + srcDir = p.rootdir.Join(srcDir).String() } else { - srcDir = filepath.Join(p.dir, srcDir) + srcDir = p.dir.Join(srcDir).String() } - if srcDir == p.dir { + if srcDir == p.dir.String() { return errors.E(ErrImport, srcAttr.Expr.Range(), "importing files in the same directory is not permitted") } - if strings.HasPrefix(p.dir, srcDir) { + if p.dir.HasPrefix(srcDir) { return errors.E(ErrImport, srcAttr.Expr.Range(), "importing files in the same tree is not permitted") } @@ -708,12 +707,13 @@ func (p *TerramateParser) handleImport(importBlock *ast.Block) error { "import path %q returned no matches", srcVal.AsString()) } for _, file := range matches { - if _, ok := p.parsedFiles[file]; ok { + abspath := os.NewHostPath(file) + if _, ok := p.parsedFiles[abspath]; ok { return errors.E(ErrImport, srcAttr.Expr.Range(), "file %q already parsed", file) } - st, err := os.Lstat(file) + st, err := stdos.Lstat(file) if err != nil { return errors.E( ErrImport, @@ -732,14 +732,14 @@ func (p *TerramateParser) handleImport(importBlock *ast.Block) error { ) } - fileDir := filepath.Dir(file) + fileDir := abspath.Dir() importParser, err := NewTerramateParser(p.rootdir, fileDir) if err != nil { return errors.E(ErrImport, srcAttr.Expr.Range(), err, "failed to create sub parser: %s", fileDir) } - err = importParser.AddFile(file) + err = importParser.AddFile(abspath) if err != nil { return errors.E(ErrImport, srcAttr.Expr.Range(), err) @@ -764,34 +764,34 @@ func (p *TerramateParser) handleImport(importBlock *ast.Block) error { return errors.E(ErrImport, err, "failed to merge imported configuration") } - p.addParsedFile(p.dir, external, file) + p.addParsedFile(p.dir, external, abspath) } return nil } -func (p *TerramateParser) sortedFilenames() []string { - filenames := []string{} +func (p *TerramateParser) sortedFilenames() os.Paths { + filenames := os.Paths{} for fname := range p.files { filenames = append(filenames, fname) } - sort.Strings(filenames) + slices.Sort(filenames) return filenames } -func (p *TerramateParser) sortedParsedFilenames() []string { - filenames := append([]string{}, p.internalParsedFiles()...) - sort.Strings(filenames) +func (p *TerramateParser) sortedParsedFilenames() os.Paths { + filenames := append(os.Paths{}, p.internalParsedFiles()...) + slices.Sort(filenames) return filenames } -func (p *TerramateParser) internalParsedFiles() []string { - filenames := []string{} +func (p *TerramateParser) internalParsedFiles() os.Paths { + filenames := os.Paths{} for fname, parsed := range p.parsedFiles { if parsed.kind == internal { filenames = append(filenames, fname) } } - sort.Strings(filenames) + slices.Sort(filenames) return filenames } @@ -888,8 +888,8 @@ func (p *TerramateParser) parseStack(stackblock *ast.Block) (*Stack, error) { } // NewConfig creates a new HCL config with dir as config directory path. -func NewConfig(dir string) (Config, error) { - st, err := os.Stat(dir) +func NewConfig(dir os.Path) (Config, error) { + st, err := stdos.Stat(dir.String()) if err != nil { return Config{}, errors.E(err, "initializing config") } @@ -927,7 +927,7 @@ func (c Config) Experiments() []string { } // AbsDir returns the absolute path of the configuration directory. -func (c Config) AbsDir() string { return c.absdir } +func (c Config) AbsDir() os.Path { return c.absdir } // IsEmpty returns true if the config is empty, false otherwise. func (c Config) IsEmpty() bool { @@ -944,8 +944,8 @@ func (c Config) HasGlobals() bool { // Save the configuration file using filename inside config directory. func (c Config) Save(filename string) (err error) { - cfgpath := filepath.Join(c.absdir, filename) - f, err := os.Create(cfgpath) + cfgpath := c.absdir.Join(filename) + f, err := stdos.Create(cfgpath.String()) if err != nil { return errors.E(err, "saving configuration file %q", cfgpath) } @@ -967,7 +967,7 @@ func (c Config) Save(filename string) (err error) { // using root as project workspace, parsing all files with the suffixes .tm and // .tm.hcl. It parses in non-strict mode for compatibility with older versions. // Note: it does not recurse into child directories. -func ParseDir(root string, dir string, experiments ...string) (Config, error) { +func ParseDir(root os.Path, dir os.Path, experiments ...string) (Config, error) { p, err := NewTerramateParser(root, dir, experiments...) if err != nil { return Config{}, err @@ -980,7 +980,7 @@ func ParseDir(root string, dir string, experiments ...string) (Config, error) { } // IsRootConfig parses rootdir and tells if it contains a root config or not. -func IsRootConfig(rootdir string) (bool, error) { +func IsRootConfig(rootdir os.Path) (bool, error) { p, err := NewTerramateParser(rootdir, rootdir) if err != nil { return false, err @@ -2343,7 +2343,7 @@ func (p *TerramateParser) checkConfigSanity(_ Config) error { return nil } -func terramateConfigRunSanityCheck(parsingDir string, runblock *ast.Block) error { +func terramateConfigRunSanityCheck(parsingDir os.Path, runblock *ast.Block) error { errs := errors.L() for _, attr := range runblock.Attributes.SortedList() { errs.Append(attributeSanityCheckErr(parsingDir, "terramate.config.run", attr)) @@ -2357,7 +2357,7 @@ func terramateConfigRunSanityCheck(parsingDir string, runblock *ast.Block) error return errs.AsError() } -func terramateConfigBlockSanityCheck(parsingDir string, cfgblock *ast.Block) error { +func terramateConfigBlockSanityCheck(parsingDir os.Path, cfgblock *ast.Block) error { errs := errors.L() for _, attr := range cfgblock.Attributes.SortedList() { errs.Append(attributeSanityCheckErr(parsingDir, "terramate.config", attr)) @@ -2372,9 +2372,9 @@ func terramateConfigBlockSanityCheck(parsingDir string, cfgblock *ast.Block) err return errs.AsError() } -func blockSanityCheckErr(parsingDir string, baseBlockName string, block *ast.Block) error { +func blockSanityCheckErr(parsingDir os.Path, baseBlockName string, block *ast.Block) error { err := errors.E("block %s.%s can only be declared at the project root directory", baseBlockName, block.Type) - if filepath.Dir(block.Range.HostPath()) != parsingDir { + if block.Range.HostPath().Dir() != parsingDir { err = errors.E(ErrTerramateSchema, err, block.TypeRange, "imported from directory %q", parsingDir) } else { @@ -2383,9 +2383,9 @@ func blockSanityCheckErr(parsingDir string, baseBlockName string, block *ast.Blo return err } -func attributeSanityCheckErr(parsingDir string, baseBlockName string, attr ast.Attribute) error { +func attributeSanityCheckErr(parsingDir os.Path, baseBlockName string, attr ast.Attribute) error { err := errors.E("attribute %s.%s can only be declared at the project root directory", baseBlockName, attr.Name) - if filepath.Dir(attr.Range.HostPath()) != parsingDir { + if attr.Range.HostPath().Dir() != parsingDir { err = errors.E(ErrTerramateSchema, err, attr.NameRange, "imported from directory %q", parsingDir) } else { diff --git a/hcl/hcl_cfg_run_test.go b/hcl/hcl_cfg_run_test.go index 2789cde13c..e40bb5ce19 100644 --- a/hcl/hcl_cfg_run_test.go +++ b/hcl/hcl_cfg_run_test.go @@ -4,8 +4,7 @@ package hcl_test import ( - "os" - "path/filepath" + stdos "os" "testing" "github.com/madlambda/spells/assert" @@ -32,11 +31,11 @@ func TestHCLParserConfigRun(t *testing.T) { // we need an origin file/data to get the tokens for each expression, // hence all this x_x. rootdir := test.TempDir(t) - filepath := filepath.Join(rootdir, "test_file.hcl") - assert.NoError(t, os.WriteFile(filepath, []byte(rawattributes), 0700)) + filepath := rootdir.Join("test_file.hcl") + assert.NoError(t, stdos.WriteFile(filepath.String(), []byte(rawattributes), 0700)) parser := hclparse.NewParser() - res, diags := parser.ParseHCLFile(filepath) + res, diags := parser.ParseHCLFile(filepath.String()) if diags.HasErrors() { t.Fatalf("test case provided invalid hcl, error: %v hcl:\n%s", diags, rawattributes) } diff --git a/hcl/hcl_import_test.go b/hcl/hcl_import_test.go index e7f59d6405..6910948455 100644 --- a/hcl/hcl_import_test.go +++ b/hcl/hcl_import_test.go @@ -8,6 +8,7 @@ import ( "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/hcl" + "github.com/terramate-io/terramate/project" . "github.com/terramate-io/terramate/test/hclutils" ) @@ -49,7 +50,7 @@ func TestHCLImport(t *testing.T) { }, { name: "import with non-existent file - fails", - parsedir: "stack", + parsedir: project.NewPath("/stack"), input: []cfgfile{ { filename: "stack/cfg.tm", @@ -101,7 +102,7 @@ func TestHCLImport(t *testing.T) { }, { name: "import cycle - fails", - parsedir: "stack", + parsedir: project.NewPath("/stack"), input: []cfgfile{ { filename: "stack/cfg.tm", @@ -125,7 +126,7 @@ func TestHCLImport(t *testing.T) { }, { name: "import same tree - fails", - parsedir: "stack", + parsedir: project.NewPath("/stack"), input: []cfgfile{ { filename: "stack/cfg.tm", @@ -148,7 +149,7 @@ func TestHCLImport(t *testing.T) { }, { name: "import same file multiple times - fails", - parsedir: "stack", + parsedir: project.NewPath("/stack"), input: []cfgfile{ { filename: "stack/cfg.tm", @@ -176,7 +177,7 @@ func TestHCLImport(t *testing.T) { }, { name: "imported file imports same file multiple times - fails", - parsedir: "stack", + parsedir: project.NewPath("/stack"), input: []cfgfile{ { filename: "stack/cfg.tm", @@ -208,7 +209,7 @@ func TestHCLImport(t *testing.T) { }, { name: "import disjoint directory with unexpected terramate block", - parsedir: "stack", + parsedir: project.NewPath("/stack"), input: []cfgfile{ { filename: "/stack/cfg.tm", @@ -231,7 +232,7 @@ func TestHCLImport(t *testing.T) { }, { name: "import disjoint directory", - parsedir: "stack", + parsedir: project.NewPath("/stack"), input: []cfgfile{ { filename: "/stack/cfg.tm", @@ -252,7 +253,7 @@ func TestHCLImport(t *testing.T) { }, { name: "import relative directory", - parsedir: "stack", + parsedir: project.NewPath("/stack"), input: []cfgfile{ { filename: "/stack/cfg.tm", @@ -271,8 +272,8 @@ func TestHCLImport(t *testing.T) { }, { name: "import relative directory outside terramate root", - parsedir: "project/stack", - rootdir: "project", + parsedir: project.NewPath("/project/stack"), + rootdir: project.NewPath("project"), input: []cfgfile{ { filename: "/project/stack/cfg.tm", @@ -294,7 +295,7 @@ func TestHCLImport(t *testing.T) { }, { name: "import with redefinition of top-level attributes", - parsedir: "stack", + parsedir: project.NewPath("stack"), input: []cfgfile{ { filename: "stack/cfg.tm", @@ -318,7 +319,7 @@ func TestHCLImport(t *testing.T) { }, { name: "import stacks - fails", - parsedir: "stack", + parsedir: project.NewPath("/stack"), input: []cfgfile{ { filename: "stack/cfg.tm", diff --git a/hcl/hcl_script_test.go b/hcl/hcl_script_test.go index 5bffb2376e..838d5e863c 100644 --- a/hcl/hcl_script_test.go +++ b/hcl/hcl_script_test.go @@ -10,6 +10,7 @@ import ( "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/hcl" "github.com/terramate-io/terramate/hcl/ast" + "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/test" . "github.com/terramate-io/terramate/test/hclutils" ) @@ -679,7 +680,7 @@ func TestHCLScript(t *testing.T) { }, { name: "script inside a stack dir", - parsedir: "stack", + parsedir: project.NewPath("/stack"), loadExperimentsConfig: true, input: []cfgfile{ { diff --git a/hcl/hcl_stack_test.go b/hcl/hcl_stack_test.go index b3cecbb253..6df4559673 100644 --- a/hcl/hcl_stack_test.go +++ b/hcl/hcl_stack_test.go @@ -9,6 +9,7 @@ import ( "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/hcl" "github.com/terramate-io/terramate/hcl/eval" + "github.com/terramate-io/terramate/project" . "github.com/terramate-io/terramate/test/hclutils" ) @@ -17,7 +18,7 @@ func TestHCLParserStack(t *testing.T) { { name: "empty stack block with terramate block not in root works in nonStrict mode", nonStrict: true, - parsedir: "stack", + parsedir: project.NewPath("/stack"), input: []cfgfile{ { filename: "stack/stack.tm", diff --git a/hcl/hcl_test.go b/hcl/hcl_test.go index 87b8526b21..98b51eb13a 100644 --- a/hcl/hcl_test.go +++ b/hcl/hcl_test.go @@ -12,6 +12,8 @@ import ( "github.com/rs/zerolog" "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/hcl" + "github.com/terramate-io/terramate/os" + "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/safeguard" "github.com/terramate-io/terramate/test" errtest "github.com/terramate-io/terramate/test/errors" @@ -33,12 +35,16 @@ type ( testcase struct { name string nonStrict bool - parsedir string + parsedir project.Path + rootdir project.Path // whether the experiments config should be loaded from rootdir loadExperimentsConfig bool - rootdir string - input []cfgfile - want want + + internal struct { + rootdir os.Path + } + input []cfgfile + want want } ) @@ -1596,7 +1602,7 @@ func TestHCLParserTerramateBlocksMerging(t *testing.T) { }, { name: "terramate.config.git in non-root directory fails", - parsedir: "stack", + parsedir: project.NewPath("/stack"), input: []cfgfile{ { filename: "stack/stack.tm", @@ -1676,25 +1682,24 @@ func testParser(t *testing.T, tc testcase) { if inputConfigFile.filename == "" { panic("expect a filename in the input config") } - path := filepath.Join(configsDir, inputConfigFile.filename) - dir := filepath.Dir(path) - filename := filepath.Base(path) + path := configsDir.Join(inputConfigFile.filename) + dir := path.Dir() + filename := filepath.Base(path.String()) test.WriteFile(t, dir, filename, inputConfigFile.body) } FixupFiledirOnErrorsFileRanges(configsDir, tc.want.errs) info.FixRangesOnConfig(configsDir, tc.want.config) - if tc.parsedir == "" { - tc.parsedir = configsDir - } else { - tc.parsedir = filepath.Join(configsDir, tc.parsedir) + if tc.parsedir.String() == "" { + tc.parsedir = project.NewPath("/") } - if tc.rootdir == "" { - tc.rootdir = configsDir + if tc.internal.rootdir.String() == "" { + tc.internal.rootdir = configsDir } else { - tc.rootdir = filepath.Join(configsDir, tc.rootdir) + tc.internal.rootdir = configsDir.Join(tc.rootdir.String()) } + got, err := parse(tc) errtest.AssertErrorList(t, err, tc.want.errs) @@ -1748,23 +1753,24 @@ func parse(tc testcase) (hcl.Config, error) { err error ) + dir := tc.internal.rootdir.Join(tc.parsedir.String()) if tc.nonStrict { - parser, err = hcl.NewTerramateParser(tc.rootdir, tc.parsedir) + parser, err = hcl.NewTerramateParser(tc.internal.rootdir, dir) } else { - parser, err = hcl.NewStrictTerramateParser(tc.rootdir, tc.parsedir) + parser, err = hcl.NewStrictTerramateParser(tc.internal.rootdir, dir) } if err != nil { return hcl.Config{}, err } - err = parser.AddDir(tc.parsedir) + err = parser.AddDir(dir) if err != nil { return hcl.Config{}, errors.E("adding files to parser", err) } if tc.loadExperimentsConfig { - rootcfg, err := hcl.ParseDir(tc.rootdir, tc.rootdir) + rootcfg, err := hcl.ParseDir(tc.internal.rootdir, dir) if err != nil { return hcl.Config{}, errors.E("failed to load root config", err) } @@ -1818,7 +1824,7 @@ func TestHCLParseProvidesAllParsedBodies(t *testing.T) { _, err = parser.ParseConfig() assert.NoError(t, err) - cfgpath := filepath.Join(cfgdir, filename) + cfgpath := cfgdir.Join(filename) bodies := parser.ParsedBodies() body, ok := bodies[cfgpath] diff --git a/hcl/info/range.go b/hcl/info/range.go index c6c941db10..40a3979030 100644 --- a/hcl/info/range.go +++ b/hcl/info/range.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/terramate-io/hcl/v2" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" ) @@ -23,7 +24,7 @@ type Pos struct { // Range represents a span of characters between two positions in a source file. type Range struct { - hostpath string + hostpath os.Path path project.Path start, end Pos } @@ -34,13 +35,16 @@ type Ranges []Range // NewRange creates a new Range from the given [hcl.Range] and the rootdir. // This function assumes that the filename on the given [hcl.Range] is // absolute and inside rootdir. -func NewRange(rootdir string, r hcl.Range) Range { - return Range{ - path: project.PrjAbsPath(rootdir, r.Filename), - hostpath: r.Filename, - start: NewPos(r.Start), - end: NewPos(r.End), +func NewRange(rootdir os.Path, r hcl.Range) Range { + info := Range{ + start: NewPos(r.Start), + end: NewPos(r.End), } + if r.Filename != "" { + info.hostpath = os.NewHostPath(r.Filename) + info.path = project.PrjAbsPath(rootdir, info.hostpath) + } + return info } // NewPos creates a new Pos from the given [hcl.Pos]. @@ -54,7 +58,7 @@ func NewPos(p hcl.Pos) Pos { // HostPath is the name of the file into which this range's positions point. // It is always an absolute path on the host filesystem. -func (r Range) HostPath() string { +func (r Range) HostPath() os.Path { return r.hostpath } @@ -123,7 +127,7 @@ func (p Pos) Byte() int { // ToHCLRange converts Range to [hcl.Range]. func (r Range) ToHCLRange() hcl.Range { return hcl.Range{ - Filename: r.HostPath(), + Filename: r.HostPath().String(), Start: hcl.Pos{ Byte: r.Start().Byte(), Line: r.Start().Line(), diff --git a/hcl/info/range_test.go b/hcl/info/range_test.go index 25c840a0d0..bce28e608b 100644 --- a/hcl/info/range_test.go +++ b/hcl/info/range_test.go @@ -4,12 +4,13 @@ package info_test import ( - "os" + stdos "os" "path/filepath" "testing" "github.com/madlambda/spells/assert" "github.com/terramate-io/terramate/hcl/info" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/test" . "github.com/terramate-io/terramate/test/hclutils" @@ -23,10 +24,10 @@ func TestRangeFromHCLRange(t *testing.T) { path := filepath.Join("dir", "sub", "assert.tm") start := Start(1, 1, 0) end := End(3, 2, 37) - hclrange := Mkrange(filepath.Join(rootdir, path), start, end) + hclrange := Mkrange(rootdir.Join(path), start, end) tmrange := info.NewRange(rootdir, hclrange) - assert.EqualStrings(t, hclrange.Filename, tmrange.HostPath()) + assert.EqualStrings(t, hclrange.Filename, tmrange.HostPath().String()) assertRangePath(t, tmrange, path) assert.EqualInts(t, hclrange.Start.Line, tmrange.Start().Line()) @@ -42,14 +43,14 @@ func TestRangeStrRepr(t *testing.T) { t.Parallel() rootdir := test.TempDir(t) tmrange := info.NewRange(rootdir, Mkrange( - filepath.Join(rootdir, "dir", "assert.tm"), + rootdir.Join("dir", "assert.tm"), Start(1, 1, 0), End(3, 2, 37), )) assert.EqualStrings(t, "/dir/assert.tm:1,1-3,2", tmrange.String()) tmrange = info.NewRange(rootdir, Mkrange( - filepath.Join(rootdir, "assert.tm"), + rootdir.Join("assert.tm"), Start(1, 1, 0), End(1, 2, 37), )) @@ -62,23 +63,23 @@ func TestRangeWithFileOnRootdir(t *testing.T) { path := "assert.tm" start := Start(0, 0, 0) end := End(0, 0, 0) - hclrange := Mkrange(filepath.Join(rootdir, path), start, end) + hclrange := Mkrange(rootdir.Join(path), start, end) tmrange := info.NewRange(rootdir, hclrange) - assert.EqualStrings(t, hclrange.Filename, tmrange.HostPath()) + assert.EqualStrings(t, hclrange.Filename, tmrange.HostPath().String()) assertRangePath(t, tmrange, path) } func TestRangeOnRootWithFileOnRootdir(t *testing.T) { t.Parallel() - rootdir := string(os.PathSeparator) + rootdir := os.NewHostPath(string(stdos.PathSeparator)) path := "assert.tm" start := Start(0, 0, 0) end := End(0, 0, 0) - hclrange := Mkrange(filepath.Join(rootdir, path), start, end) + hclrange := Mkrange(rootdir.Join(path), start, end) tmrange := info.NewRange(rootdir, hclrange) - assert.EqualStrings(t, hclrange.Filename, tmrange.HostPath()) + assert.EqualStrings(t, hclrange.Filename, tmrange.HostPath().String()) assertRangePath(t, tmrange, path) } diff --git a/hcl/raw_config.go b/hcl/raw_config.go index b5c3745f52..e872a78d25 100644 --- a/hcl/raw_config.go +++ b/hcl/raw_config.go @@ -4,10 +4,9 @@ package hcl import ( - "path/filepath" - "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/hcl/ast" + "github.com/terramate-io/terramate/os" ) // RawConfig is the configuration (attributes and blocks) without schema @@ -167,6 +166,6 @@ func (cfg *RawConfig) mergeAttrs(other ast.Attributes) error { return errs.AsError() } -func sameDir(file1, file2 string) bool { - return filepath.Dir(file1) == filepath.Dir(file2) +func sameDir(file1, file2 os.Path) bool { + return file1.Dir() == file2.Dir() } diff --git a/ls/commands.go b/ls/commands.go index b3e3e34e9c..b147199d32 100644 --- a/ls/commands.go +++ b/ls/commands.go @@ -13,6 +13,7 @@ import ( "github.com/rs/zerolog/log" "github.com/terramate-io/terramate/config" "github.com/terramate-io/terramate/errors" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/stack" "go.lsp.dev/jsonrpc2" @@ -81,7 +82,7 @@ func (s *Server) createStack(params lsp.ExecuteCommandParams) error { return errors.E(ErrCreateStackInvalidArgument, err, "failed to parse URI: %s", argVal) } - stackConfig.Dir = project.PrjAbsPath(s.workspace, dir.Filename()) + stackConfig.Dir = project.PrjAbsPath(s.workspace, os.NewHostPath(dir.Filename())) case "genid": id, err := uuid.NewRandom() if err != nil { diff --git a/ls/commands_test.go b/ls/commands_test.go index 2284e16a8b..55f94beb1b 100644 --- a/ls/commands_test.go +++ b/ls/commands_test.go @@ -5,7 +5,6 @@ package tmls_test import ( "fmt" - "path/filepath" "strings" "testing" @@ -13,6 +12,7 @@ import ( "github.com/terramate-io/terramate/config" "github.com/terramate-io/terramate/errors" tmls "github.com/terramate-io/terramate/ls" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/test" lstest "github.com/terramate-io/terramate/test/ls" @@ -192,7 +192,7 @@ func TestCreateGenerateID(t *testing.T) { res, err := f.Editor.Command(lsp.ExecuteCommandParams{ Command: "terramate.createStack", Arguments: []interface{}{ - "uri=" + uri.File(filepath.Join(f.Sandbox.RootDir(), "my-stack")), + "uri=" + uri.File(f.Sandbox.RootDir().Join("my-stack").String()), "genid=true", }, }) @@ -204,7 +204,7 @@ func TestCreateGenerateID(t *testing.T) { assert.IsTrue(t, gotStack.ID != "", "id was not generated") } -func mkCreateArgs(rootdir string, args []string) (retArgs []interface{}) { +func mkCreateArgs(rootdir os.Path, args []string) (retArgs []interface{}) { if len(args) == 0 { panic("no arguments") } @@ -214,7 +214,7 @@ func mkCreateArgs(rootdir string, args []string) (retArgs []interface{}) { argVal := arg[pos+1:] switch argName { case "uri": - argVal = string(uri.File(filepath.Join(rootdir, argVal))) + argVal = string(uri.File(rootdir.Join(argVal).String())) case "name": case "description": default: diff --git a/ls/ls.go b/ls/ls.go index 1c4c48eda2..d9325ada52 100644 --- a/ls/ls.go +++ b/ls/ls.go @@ -8,9 +8,9 @@ import ( "context" "encoding/json" "fmt" - "os" + stdos "os" "path/filepath" - "sort" + "slices" "strings" "github.com/rs/zerolog" @@ -18,6 +18,7 @@ import ( "github.com/terramate-io/terramate/config" "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/hcl" + "github.com/terramate-io/terramate/os" "go.lsp.dev/jsonrpc2" lsp "go.lsp.dev/protocol" "go.lsp.dev/uri" @@ -29,7 +30,7 @@ const MethodExecuteCommand = "workspace/executeCommand" // Server is the Language Server. type Server struct { conn jsonrpc2.Conn - workspace string + workspace os.Path handlers handlers log zerolog.Logger @@ -78,7 +79,7 @@ func (s *Server) buildHandlers() { func (s *Server) Handler(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error { logger := s.log.With(). Str("action", "server.Handler()"). - Str("workspace", s.workspace). + Stringer("workspace", s.workspace). Str("method", r.Method()). Logger() @@ -113,7 +114,7 @@ func (s *Server) handleInitialize( return jsonrpc2.ErrInvalidParams } - s.workspace = string(uri.New(params.RootURI).Filename()) + s.workspace = os.NewHostPath(uri.New(params.RootURI).Filename()) err := reply(ctx, lsp.InitializeResult{ Capabilities: lsp.ServerCapabilities{ CompletionProvider: &lsp.CompletionOptions{}, @@ -178,7 +179,8 @@ func (s *Server) handleDocumentOpen( return jsonrpc2.ErrParse } - fname := params.TextDocument.URI.Filename() + // if the client behaves, the URI will be absolute. + fname := os.NewHostPath(params.TextDocument.URI.Filename()) content := params.TextDocument.Text return s.checkAndReply(ctx, reply, fname, content) @@ -203,7 +205,7 @@ func (s *Server) handleDocumentChange( } content := params.ContentChanges[0].Text - fname := params.TextDocument.URI.Filename() + fname := os.NewHostPath(params.TextDocument.URI.Filename()) return s.checkAndReply(ctx, reply, fname, content) } @@ -220,8 +222,8 @@ func (s *Server) handleDocumentSaved( return jsonrpc2.ErrParse } - fname := params.TextDocument.URI.Filename() - content, err := os.ReadFile(fname) + fname := os.NewHostPath(params.TextDocument.URI.Filename()) + content, err := stdos.ReadFile(fname.String()) if err != nil { log.Error().Err(err).Msg("reading saved file.") return nil @@ -233,7 +235,7 @@ func (s *Server) handleDocumentSaved( // sendErrorDiagnostics sends diagnostics for each provided file, the ones with // no reported error gets an empty list of diagnostics, so the editor can clean // up its problems panel for it. -func (s *Server) sendErrorDiagnostics(ctx context.Context, files []string, err error) error { +func (s *Server) sendErrorDiagnostics(ctx context.Context, files os.Paths, err error) error { errs := errors.L() switch e := err.(type) { case *errors.Error: @@ -247,7 +249,7 @@ func (s *Server) sendErrorDiagnostics(ctx context.Context, files []string, err e } } - diagsMap := map[string][]lsp.Diagnostic{} + diagsMap := map[os.Path][]lsp.Diagnostic{} for _, filename := range files { diagsMap[filename] = []lsp.Diagnostic{} } @@ -261,14 +263,21 @@ func (s *Server) sendErrorDiagnostics(ctx context.Context, files []string, err e log.Debug().Str("error", e.Detailed()).Msg("sending diagnostics") + // filename can be relative filename := e.FileRange.Filename + var absFilename os.Path + if filepath.IsAbs(filename) { + absFilename = os.NewHostPath(filename) + } else { + absFilename = os.HostPath(s.workspace, filename) + } fileRange := lsp.Range{} fileRange.Start.Line = uint32(e.FileRange.Start.Line) - 1 fileRange.Start.Character = uint32(e.FileRange.Start.Column) - 1 fileRange.End.Line = uint32(e.FileRange.End.Line) - 1 fileRange.End.Character = uint32(e.FileRange.End.Column) - 1 - diagsMap[filename] = append(diagsMap[filename], lsp.Diagnostic{ + diagsMap[absFilename] = append(diagsMap[absFilename], lsp.Diagnostic{ Message: e.Message(), Range: fileRange, Severity: lsp.DiagnosticSeverityError, @@ -278,7 +287,7 @@ func (s *Server) sendErrorDiagnostics(ctx context.Context, files []string, err e for _, filename := range files { diags := diagsMap[filename] - filePath := lsp.URI(uri.File(filepath.ToSlash(filename))) + filePath := lsp.URI(uri.File(filepath.ToSlash(filename.String()))) s.sendDiagnostics(ctx, filePath, diags) } @@ -314,12 +323,12 @@ func (s *Server) sendDiagnostics(ctx context.Context, uri lsp.URI, diags []lsp.D func (s *Server) checkAndReply( ctx context.Context, reply jsonrpc2.Replier, - fname string, + fname os.Path, content string, ) error { files, err := listFiles(fname) files = append(files, fname) - sort.Strings(files) + slices.Sort(files) if err == nil { err = s.checkFiles(files, fname, content) } @@ -329,14 +338,14 @@ func (s *Server) checkAndReply( ) } -func listFiles(fromFile string) ([]string, error) { - dir := filepath.Dir(fromFile) - dirEntries, err := os.ReadDir(dir) +func listFiles(fromFile os.Path) (os.Paths, error) { + dir := fromFile.Dir() + dirEntries, err := stdos.ReadDir(dir.String()) if err != nil { return nil, err } - files := []string{} + files := os.Paths{} for _, dirEntry := range dirEntries { if dirEntry.IsDir() { continue @@ -344,24 +353,21 @@ func listFiles(fromFile string) ([]string, error) { filename := dirEntry.Name() if strings.HasSuffix(filename, ".tm") || strings.HasSuffix(filename, ".tm.hcl") { - path := filepath.Join(dir, filename) - + path := dir.Join(filename) if path == fromFile { // ignore source file continue } - files = append(files, path) } } - return files, nil } // checkFiles checks if the given provided files have errors but the currentFile // is handled separately because it can be unsaved. -func (s *Server) checkFiles(files []string, currentFile string, currentContent string) error { - dir := filepath.Dir(currentFile) +func (s *Server) checkFiles(files []os.Path, currentFile os.Path, currentContent string) error { + dir := currentFile.Dir() var experiments []string root, rootdir, found, err := config.TryLoadConfig(dir) if !found { @@ -384,7 +390,7 @@ func (s *Server) checkFiles(files []string, currentFile string, currentContent s if currentFile == fname { contents = []byte(currentContent) } else { - contents, err = os.ReadFile(fname) + contents, err = stdos.ReadFile(fname.String()) } if err != nil { diff --git a/ls/ls_test.go b/ls/ls_test.go index f62d67fcc6..a5ff3805e6 100644 --- a/ls/ls_test.go +++ b/ls/ls_test.go @@ -6,7 +6,6 @@ package tmls_test import ( "encoding/json" "fmt" - "path/filepath" "sort" "testing" "time" @@ -41,8 +40,10 @@ func TestDocumentOpen(t *testing.T) { var params lsp.PublishDiagnosticsParams assert.NoError(t, json.Unmarshal(r.Params(), ¶ms), "unmarshaling params") assert.EqualInts(t, 0, len(params.Diagnostics)) - assert.EqualStrings(t, filepath.Join(stack.Path(), stackpkg.DefaultFilename), - params.URI.Filename()) + assert.EqualStrings(t, + stack.Path().Join(stackpkg.DefaultFilename).String(), + params.URI.Filename(), + ) } func TestDocumentOpenWithoutRootConfig(t *testing.T) { @@ -59,7 +60,7 @@ func TestDocumentOpenWithoutRootConfig(t *testing.T) { var params lsp.PublishDiagnosticsParams assert.NoError(t, json.Unmarshal(r.Params(), ¶ms), "unmarshaling params") assert.EqualInts(t, 0, len(params.Diagnostics)) - assert.EqualStrings(t, filepath.Join(stack.Path(), stackpkg.DefaultFilename), + assert.EqualStrings(t, stack.Path().Join(stackpkg.DefaultFilename).String(), params.URI.Filename()) } @@ -95,7 +96,7 @@ func TestDocumentRegressionErrorLoadingRootConfig(t *testing.T) { assert.EqualStrings(t, "textDocument/publishDiagnostics", rp.req.Method(), "unexpected notification request") assert.EqualInts(t, 0, len(rp.p.Diagnostics), "got diagnostics for file %v: %v", rp.p.URI.Filename(), rp.p.Diagnostics) - assert.EqualStrings(t, filepath.Join(f.Sandbox.RootDir(), "root.config.tm"), rp.p.URI.Filename()) + assert.EqualStrings(t, f.Sandbox.RootDir().Join("root.config.tm").String(), rp.p.URI.Filename()) rp = reqs[1] assert.EqualStrings(t, "textDocument/publishDiagnostics", rp.req.Method(), @@ -494,9 +495,9 @@ stack { } { t.Run(tc.name, func(t *testing.T) { f := lstest.Setup(t, tc.layout...) - workspace := tc.wrk - if workspace == "" { - workspace = f.Sandbox.RootDir() + workspace := f.Sandbox.RootDir() + if tc.wrk != "" { + workspace = f.Sandbox.RootDir().Join(tc.wrk) } f.Editor.CheckInitialize(workspace) @@ -505,7 +506,7 @@ stack { want := tc.want[i] // fix the wanted path as it depends on the sandbox root. - want.URI = uri.File(filepath.Join(f.Sandbox.RootDir(), string(want.URI))) + want.URI = uri.File(f.Sandbox.RootDir().Join(string(want.URI)).String()) select { case gotReq := <-f.Editor.Requests: assert.EqualStrings(t, lsp.MethodTextDocumentPublishDiagnostics, diff --git a/mapexpr/map.go b/mapexpr/map.go index 7f7ef8ff0e..d1739963d3 100644 --- a/mapexpr/map.go +++ b/mapexpr/map.go @@ -94,7 +94,7 @@ func NewMapExpr(block *ast.MergedBlock) (*MapExpr, error) { // Range of the map block. func (m *MapExpr) Range() hhcl.Range { return hhcl.Range{ - Filename: m.Origin.HostPath(), + Filename: m.Origin.HostPath().String(), Start: hhcl.Pos{ Byte: m.Origin.Start().Byte(), Column: m.Origin.Start().Column(), diff --git a/modvendor/download/download.go b/modvendor/download/download.go index 0943d39478..2523c51d3d 100644 --- a/modvendor/download/download.go +++ b/modvendor/download/download.go @@ -7,7 +7,7 @@ package download import ( "fmt" iofs "io/fs" - "os" + stdos "os" "path/filepath" "sort" "strings" @@ -20,6 +20,7 @@ import ( "github.com/terramate-io/terramate/git" "github.com/terramate-io/terramate/modvendor" "github.com/terramate-io/terramate/modvendor/manifest" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/tf" "github.com/zclconf/go-cty/cty" @@ -44,7 +45,7 @@ const ( type modinfo struct { source string vendoredAt project.Path - origin string + origin os.Path subdir string } @@ -73,7 +74,7 @@ type modinfo struct { // // It returns a report of everything vendored and ignored (with a reason). func Vendor( - rootdir string, + rootdir os.Path, vendorDir project.Path, modsrc tf.Source, events ProgressEventStream, @@ -91,7 +92,7 @@ func Vendor( // When vendorRequests is closed it will close the returned [Report] channel, // indicating that no more processing will be done. func HandleVendorRequests( - rootdir string, + rootdir os.Path, vendorRequests <-chan event.VendorRequest, progressEvents ProgressEventStream, ) <-chan Report { @@ -99,7 +100,7 @@ func HandleVendorRequests( go func() { logger := log.With(). Str("action", "download.HandleVendorRequests()"). - Str("rootdir", rootdir). + Stringer("rootdir", rootdir). Logger() logger.Debug().Msg("starting vendor request handler") @@ -171,16 +172,16 @@ func MergeVendorReports(reports <-chan Report) <-chan Report { // It will scan all .tf files in the directory and vendor each module declaration // containing the supported remote source URLs. func VendorAll( - rootdir string, + rootdir os.Path, vendorDir project.Path, - tfdir string, + tfdir os.Path, events ProgressEventStream, ) Report { return vendorAll(rootdir, vendorDir, tfdir, NewReport(vendorDir), events) } func vendor( - rootdir string, + rootdir os.Path, vendorDir project.Path, modsrc tf.Source, report Report, @@ -225,7 +226,7 @@ func newSourcesInfo() *sourcesInfo { } } -func (s *sourcesInfo) append(source, path string) { +func (s *sourcesInfo) append(source string, path os.Path) { if _, ok := s.set[source]; ok { return } @@ -248,9 +249,9 @@ func (s *sourcesInfo) delete(source string) { } func vendorAll( - rootdir string, + rootdir os.Path, vendorDir project.Path, - tfdir string, + tfdir os.Path, report Report, events ProgressEventStream, ) Report { @@ -258,7 +259,7 @@ func vendorAll( originMap := map[string]struct{}{} errs := errors.L(report.Error) - err := filepath.WalkDir(tfdir, func(path string, d iofs.DirEntry, err error) error { + err := filepath.WalkDir(tfdir.String(), func(path string, d iofs.DirEntry, err error) error { if err != nil { return err } @@ -271,7 +272,7 @@ func vendorAll( return nil } - modules, err := tf.ParseModules(path) + modules, err := tf.ParseModules(os.NewHostPath(path)) if err != nil { errs.Append(err) return nil @@ -284,7 +285,7 @@ func vendorAll( originMap[path] = struct{}{} - sources.append(mod.Source, path) + sources.append(mod.Source, os.NewHostPath(path)) } return nil }) @@ -303,7 +304,7 @@ func vendorAll( info.subdir = modsrc.Subdir targetVendorDir := modvendor.AbsVendorDir(rootdir, vendorDir, modsrc) - st, err := os.Stat(targetVendorDir) + st, err := stdos.Stat(targetVendorDir.String()) if err == nil && st.IsDir() { info.vendoredAt = modvendor.TargetDir(vendorDir, modsrc) continue @@ -333,29 +334,30 @@ func vendorAll( // This function is not recursive, so dependencies won't have their dependencies // vendored. See Vendor() for a recursive vendoring function. func downloadVendor( - rootdir string, + rootdir os.Path, vendorDir project.Path, modsrc tf.Source, events ProgressEventStream, -) (string, error) { +) (os.Path, error) { if modsrc.Ref == "" { return "", errors.E(ErrModRefEmpty, "ref: %v", modsrc) } modVendorDir := modvendor.AbsVendorDir(rootdir, vendorDir, modsrc) - if _, err := os.Stat(modVendorDir); err == nil { + if _, err := stdos.Stat(modVendorDir.String()); err == nil { return "", errors.E(ErrAlreadyVendored, "dir %q exists", modVendorDir) } // We want an initial temporary dir outside of the Terramate project // to do the clone since some git setups will assume that any // git clone inside a repo is a submodule. - clonedRepoDir, err := os.MkdirTemp("", ".tmvendor") + clonedRepoTmpDir, err := stdos.MkdirTemp("", ".tmvendor") if err != nil { return "", errors.E(err, "creating tmp clone dir") } + clonedRepoAbsDir := os.NewHostPath(clonedRepoTmpDir) defer func() { - if err := os.RemoveAll(clonedRepoDir); err != nil { + if err := stdos.RemoveAll(clonedRepoAbsDir.String()); err != nil { log.Warn().Err(err). Msg("deleting tmp clone dir") } @@ -366,12 +368,13 @@ func downloadVendor( // leave any changes in the project vendor dir. The final step then will // be an atomic op using rename, which probably wont fail since the temp dir is // inside the project and the whole project is most likely on the same fs/device. - tmTempDir, err := os.MkdirTemp(rootdir, ".tmvendor") + tmTempDir, err := stdos.MkdirTemp(rootdir.String(), ".tmvendor") if err != nil { return "", errors.E(err, "creating tmp dir inside project") } + targetTempAbsDir := os.NewHostPath(tmTempDir) defer func() { - if err := os.RemoveAll(tmTempDir); err != nil { + if err := stdos.RemoveAll(tmTempDir); err != nil { log.Warn().Err(err). Msg("deleting temp dir inside terramate project") } @@ -380,9 +383,9 @@ func downloadVendor( // Same strategy used on the Go toolchain: // - https://github.com/golang/go/blob/2ebe77a2fda1ee9ff6fd9a3e08933ad1ebaea039/src/cmd/go/internal/get/get.go#L129 - env := append(os.Environ(), "GIT_TERMINAL_PROMPT=0") + env := append(stdos.Environ(), "GIT_TERMINAL_PROMPT=0") g, err := git.WithConfig(git.Config{ - WorkingDir: clonedRepoDir, + WorkingDir: clonedRepoAbsDir, AllowPorcelain: true, Env: env, }) @@ -403,7 +406,7 @@ func downloadVendor( Msg("dropped progress event, event handler is not fast enough or absent") } - if err := g.Clone(modsrc.URL, clonedRepoDir); err != nil { + if err := g.Clone(modsrc.URL, clonedRepoAbsDir); err != nil { return "", err } @@ -413,35 +416,35 @@ func downloadVendor( return "", errors.E(err, "checking ref %s", modsrc.Ref) } - if err := os.RemoveAll(filepath.Join(clonedRepoDir, ".git")); err != nil { + if err := stdos.RemoveAll(filepath.Join(clonedRepoTmpDir, ".git")); err != nil { return "", errors.E(err, "removing .git dir from cloned repo") } - matcher, err := manifest.LoadFileMatcher(clonedRepoDir) + matcher, err := manifest.LoadFileMatcher(clonedRepoAbsDir) if err != nil { return "", err } - const pathSeparator string = string(os.PathSeparator) + const pathSeparator string = string(stdos.PathSeparator) - fileFilter := func(path string, entry os.DirEntry) bool { + fileFilter := func(path os.Path, entry stdos.DirEntry) bool { if entry.IsDir() { return true } - abspath := filepath.Join(path, entry.Name()) - relpath := strings.TrimPrefix(abspath, clonedRepoDir+pathSeparator) + abspath := path.Join(entry.Name()) + relpath := strings.TrimPrefix(abspath.String(), clonedRepoTmpDir+pathSeparator) return matcher.Match(strings.Split(relpath, pathSeparator), entry.IsDir()) } - if err := fs.CopyDir(tmTempDir, clonedRepoDir, fileFilter); err != nil { + if err := fs.CopyDir(targetTempAbsDir, clonedRepoAbsDir, fileFilter); err != nil { return "", errors.E(err, "copying cloned module") } - if err := os.MkdirAll(filepath.Dir(modVendorDir), 0775); err != nil { + if err := stdos.MkdirAll(modVendorDir.Dir().String(), 0775); err != nil { return "", errors.E(err, "creating mod dir inside vendor") } - if err := os.Rename(tmTempDir, modVendorDir); err != nil { + if err := stdos.Rename(tmTempDir, modVendorDir.String()); err != nil { // Assuming that the whole Terramate project is inside the // same fs/mount/dev. return "", errors.E(err, "moving module from tmp dir to vendor") @@ -449,10 +452,10 @@ func downloadVendor( return modVendorDir, nil } -func patchFiles(rootdir string, files []string, sources *sourcesInfo) error { +func patchFiles(rootdir os.Path, files []string, sources *sourcesInfo) error { errs := errors.L() for _, fname := range files { - bytes, err := os.ReadFile(fname) + bytes, err := stdos.ReadFile(fname) if err != nil { errs.Append(err) continue @@ -483,7 +486,7 @@ func patchFiles(rootdir string, files []string, sources *sourcesInfo) error { if info, ok := sources.set[sourceString]; ok && info.vendoredAt.String() != "" { relPath, err := filepath.Rel( - filepath.Dir(fname), filepath.Join(rootdir, filepath.FromSlash(info.vendoredAt.String())), + filepath.Dir(fname), rootdir.Join(filepath.FromSlash(info.vendoredAt.String())).String(), ) if err != nil { errs.Append(err) @@ -495,10 +498,10 @@ func patchFiles(rootdir string, files []string, sources *sourcesInfo) error { } } - st, err := os.Stat(fname) + st, err := stdos.Stat(fname) errs.Append(err) if err == nil { - errs.Append(os.WriteFile(fname, parsedFile.Bytes(), st.Mode())) + errs.Append(stdos.WriteFile(fname, parsedFile.Bytes(), st.Mode())) } } return errs.AsError() diff --git a/modvendor/download/download_test.go b/modvendor/download/download_test.go index 257edf49a4..01a9010363 100644 --- a/modvendor/download/download_test.go +++ b/modvendor/download/download_test.go @@ -7,7 +7,7 @@ import ( "bytes" "fmt" "io/fs" - "os" + stdos "os" "path/filepath" "strings" "testing" @@ -20,6 +20,7 @@ import ( "github.com/terramate-io/terramate/hcl" "github.com/terramate-io/terramate/modvendor" "github.com/terramate-io/terramate/modvendor/download" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/test" errtest "github.com/terramate-io/terramate/test/errors" @@ -760,7 +761,7 @@ func TestDownloadVendor(t *testing.T) { } type fixture struct { - rootdir string + rootdir os.Path vendorDir project.Path modsrc tf.Source uriModulesDir uri.URI @@ -775,12 +776,12 @@ func TestDownloadVendor(t *testing.T) { } modulesDir := s.RootDir() - uriModulesDir := uri.File(modulesDir) + uriModulesDir := uri.File(modulesDir.String()) for _, cfg := range tc.configs { test.WriteFile(t, s.RootDir(), cfg.path, applyConfigTemplate(t, cfg.data.String(), uriModulesDir)) - git := sandbox.NewGit(t, filepath.Join(modulesDir, cfg.repo)) + git := sandbox.NewGit(t, modulesDir.Join(cfg.repo)) git.CommitAll("files updated") } source := applyConfigTemplate(t, tc.source, uriModulesDir) @@ -879,11 +880,11 @@ func evaluateWantedFiles( t *testing.T, wantFiles map[vendorPathSpec]fmt.Stringer, uriModulesDir uri.URI, - rootdir string, + rootdir os.Path, vendordir project.Path, -) map[string]fmt.Stringer { +) map[os.Path]fmt.Stringer { t.Helper() - evaluated := map[string]fmt.Stringer{} + evaluated := map[os.Path]fmt.Stringer{} for pathSpec, expectedStringer := range wantFiles { pathSpecParts := strings.Split(string(pathSpec), "#") assert.EqualInts(t, len(pathSpecParts), 2) @@ -893,7 +894,7 @@ func evaluateWantedFiles( modsrc, err := tf.ParseSource(applyConfigTemplate(t, source, uriModulesDir)) assert.NoError(t, err) absVendorDir := modvendor.AbsVendorDir(rootdir, vendordir, modsrc) - evaluatedPath := filepath.Join(absVendorDir, path) + evaluatedPath := absVendorDir.Join(path) evaluated[evaluatedPath] = expectedStringer } return evaluated @@ -903,22 +904,22 @@ func checkWantedFiles( t *testing.T, tc testcase, uriModulesDir uri.URI, - rootdir string, + rootdir os.Path, vendordir project.Path, ) { t.Helper() wantFiles := evaluateWantedFiles(t, tc.wantFiles, uriModulesDir, rootdir, vendordir) - absVendorDir := filepath.Join(rootdir, tc.vendordir) + absVendorDir := rootdir.Join(tc.vendordir) - if _, err := os.Stat(absVendorDir); err != nil { - if os.IsNotExist(err) { + if _, err := stdos.Stat(absVendorDir.String()); err != nil { + if stdos.IsNotExist(err) { return } assert.Error(t, err) } - err := filepath.Walk(absVendorDir, func(path string, _ fs.FileInfo, err error) error { + err := filepath.Walk(absVendorDir.String(), func(path string, _ fs.FileInfo, err error) error { if err != nil { return err } @@ -927,7 +928,7 @@ func checkWantedFiles( return nil } - expectedStringTemplate, ok := wantFiles[path] + expectedStringTemplate, ok := wantFiles[os.NewHostPath(path)] if ok { // file must be rewritten relVendoredPaths := computeRelativePaths( @@ -938,7 +939,7 @@ func checkWantedFiles( assert.EqualStrings(t, want, got, "file %q mismatch", path) } else { // check the vendored file is the same as the one in the module dir. - originalPath := modvendor.SourceDir(path, rootdir, vendordir) + originalPath := modvendor.SourceDir(os.NewHostPath(path), rootdir, vendordir) pathEnd := filepath.ToSlash(strings.TrimPrefix(originalPath, uriModulesDir.Filename())) originalPath = strings.TrimSuffix(filepath.ToSlash(originalPath), pathEnd) @@ -946,10 +947,10 @@ func checkWantedFiles( moduleName := pathParts[1] originalPath = filepath.Join(originalPath, moduleName, strings.Join(pathParts[3:], "/")) - originalBytes, err := os.ReadFile(originalPath) + originalBytes, err := stdos.ReadFile(originalPath) assert.NoError(t, err) - gotBytes, err := os.ReadFile(path) + gotBytes, err := stdos.ReadFile(path) assert.NoError(t, err) assert.EqualStrings(t, string(originalBytes), string(gotBytes), "files %q and %q mismatch", originalPath, path) @@ -964,7 +965,7 @@ func computeRelativePaths( relativeToDir string, wantVendored []string, uriModulesDir uri.URI, - rootdir string, + rootdir os.Path, vendordir project.Path, ) []string { t.Helper() @@ -975,7 +976,8 @@ func computeRelativePaths( modsrc, err := tf.ParseSource(rawSource) assert.NoError(t, err) relPath, err := filepath.Rel(relativeToDir, - modvendor.AbsVendorDir(rootdir, vendordir, modsrc)) + modvendor.AbsVendorDir(rootdir, vendordir, modsrc).String(), + ) assert.NoError(t, err) relVendoredPaths = append(relVendoredPaths, filepath.ToSlash(relPath)) } @@ -1002,7 +1004,7 @@ func TestModVendorWithCommitIDRef(t *testing.T) { // So the initial clone gets the repo pointing at main as "default" repogit.Checkout("main") - gitURI := uri.File(repoSandbox.RootDir()) + gitURI := uri.File(repoSandbox.RootDir().String()) rootdir := test.TempDir(t) source, err := tf.ParseSource(fmt.Sprintf("git::%s?ref=%s", gitURI, ref)) @@ -1020,7 +1022,7 @@ func TestModVendorWithCommitIDRef(t *testing.T) { }, got) cloneDir := modvendor.AbsVendorDir(rootdir, vendordir, got.Vendored[modvendor.TargetDir(vendordir, source)].Source) - gotContent := test.ReadFile(t, cloneDir, filename) + gotContent := test.ReadFile(t, cloneDir.String(), filename) assert.EqualStrings(t, content, string(gotContent)) assertNoGitDir(t, cloneDir) } @@ -1040,7 +1042,7 @@ func TestModVendorWithRef(t *testing.T) { repogit := repoSandbox.Git() repogit.CommitAll("add file") - gitURI := uri.File(repoSandbox.RootDir()) + gitURI := uri.File(repoSandbox.RootDir().String()) rootdir := test.TempDir(t) source := newSource(t, gitURI, ref) @@ -1062,7 +1064,7 @@ func TestModVendorWithRef(t *testing.T) { assert.EqualStrings(t, wantCloneDir.String(), cloneDir.String()) absCloneDir := modvendor.AbsVendorDir(rootdir, vendordir, got.Vendored[vendoredAt].Source) - gotContent := test.ReadFile(t, absCloneDir, filename) + gotContent := test.ReadFile(t, absCloneDir.String(), filename) assert.EqualStrings(t, content, string(gotContent)) assertNoGitDir(t, absCloneDir) @@ -1093,10 +1095,10 @@ func TestModVendorWithRef(t *testing.T) { absCloneDir = modvendor.AbsVendorDir(rootdir, vendordir, got.Vendored[wantCloneDir].Source) assertNoGitDir(t, absCloneDir) - gotContent = test.ReadFile(t, absCloneDir, filename) + gotContent = test.ReadFile(t, absCloneDir.String(), filename) assert.EqualStrings(t, content, string(gotContent)) - gotContent = test.ReadFile(t, absCloneDir, newFilename) + gotContent = test.ReadFile(t, absCloneDir.String(), newFilename) assert.EqualStrings(t, newContent, string(gotContent)) } @@ -1109,7 +1111,7 @@ func TestModVendorDoesNothingIfRefExists(t *testing.T) { g := s.Git() g.CommitAll("add file") - gitURI := uri.File(s.RootDir()) + gitURI := uri.File(s.RootDir().String()) rootdir := test.TempDir(t) source, err := tf.ParseSource(fmt.Sprintf("git::%s?ref=main", gitURI)) @@ -1117,7 +1119,7 @@ func TestModVendorDoesNothingIfRefExists(t *testing.T) { vendordir := project.NewPath("/vendor/fun") clonedir := modvendor.AbsVendorDir(rootdir, vendordir, source) - test.MkdirAll(t, clonedir) + test.MkdirAll(t, clonedir.String()) got := download.Vendor(rootdir, vendordir, source, nil) want := download.Report{ Ignored: []download.IgnoredVendor{ @@ -1138,7 +1140,7 @@ func TestModVendorDoesNothingIfRefExists(t *testing.T) { func TestModVendorNoRefFails(t *testing.T) { t.Parallel() s := sandbox.New(t) - gitURI := uri.File(s.RootDir()) + gitURI := uri.File(s.RootDir().String()) rootdir := test.TempDir(t) source, err := tf.ParseSource(fmt.Sprintf("git::%s", gitURI)) @@ -1155,7 +1157,7 @@ func TestModVendorNoRefFails(t *testing.T) { }, report) } -func assertNoGitDir(t *testing.T, dir string) { +func assertNoGitDir(t *testing.T, dir os.Path) { t.Helper() entries := test.ReadDir(t, dir) diff --git a/modvendor/download/event_test.go b/modvendor/download/event_test.go index fa0fc86e24..c9cb7dc52c 100644 --- a/modvendor/download/event_test.go +++ b/modvendor/download/event_test.go @@ -264,13 +264,13 @@ func TestVendorEvents(t *testing.T) { t.Parallel() repositories := sandbox.NoGit(t, false) - reposURI := uri.File(repositories.RootDir()) + reposURI := uri.File(repositories.RootDir().String()) for _, repo := range tcase.repositories { repositoriesRoot := repositories.RootDir() - repoRoot := filepath.Join(repositoriesRoot, repo.name) + repoRoot := repositoriesRoot.Join(repo.name) - test.MkdirAll(t, repoRoot) + test.MkdirAll(t, repoRoot.String()) git := sandbox.NewGit(t, repoRoot) git.Init() diff --git a/modvendor/download/manifest_test.go b/modvendor/download/manifest_test.go index 90fda875ac..5d751f5f23 100644 --- a/modvendor/download/manifest_test.go +++ b/modvendor/download/manifest_test.go @@ -5,7 +5,7 @@ package download_test import ( "fmt" - "os" + stdos "os" "path/filepath" "sort" "strings" @@ -16,6 +16,7 @@ import ( "github.com/terramate-io/terramate/hcl" "github.com/terramate-io/terramate/modvendor" "github.com/terramate-io/terramate/modvendor/download" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/test" "github.com/terramate-io/terramate/test/sandbox" @@ -286,12 +287,12 @@ func TestVendorManifest(t *testing.T) { test.RemoveFile(t, repoSandbox.RootDir(), ".gitignore") for _, file := range tcase.files { - path := filepath.Join(repoSandbox.RootDir(), file) - test.WriteFile(t, filepath.Dir(path), filepath.Base(path), "") + path := repoSandbox.RootDir().Join(file) + test.WriteFile(t, path.Dir(), path.Base(), "") } for _, manifest := range tcase.manifests { - path := filepath.Join(repoSandbox.RootDir(), manifest.path) + path := repoSandbox.RootDir().Join(manifest.path) patternList := "[" for _, pattern := range manifest.patterns { patternList += fmt.Sprintf("%q,\n", pattern) @@ -304,13 +305,13 @@ func TestVendorManifest(t *testing.T) { ), ), ) - test.WriteFile(t, filepath.Dir(path), filepath.Base(path), hcldoc.String()) + test.WriteFile(t, path.Dir(), path.Base(), hcldoc.String()) } repogit := repoSandbox.Git() repogit.CommitAll("setup vendored repo") - gitURI := uri.File(repoSandbox.RootDir()) + gitURI := uri.File(repoSandbox.RootDir().String()) rootdir := test.TempDir(t) source := newSource(t, gitURI, "main") @@ -321,7 +322,7 @@ func TestVendorManifest(t *testing.T) { clonedir := modvendor.AbsVendorDir(rootdir, vendordir, source) gotFiles := listFiles(t, clonedir) for i, f := range gotFiles { - gotFiles[i] = filepath.ToSlash(strings.TrimPrefix(f, clonedir)) + gotFiles[i] = filepath.ToSlash(strings.TrimPrefix(f, clonedir.String())) } test.AssertDiff(t, gotFiles, tcase.wantFiles) }) @@ -344,31 +345,31 @@ func testInvalidManifestFails(t *testing.T, configpath string) { repogit := repoSandbox.Git() repogit.CommitAll("setup vendored repo") - gitURL := uri.File(repoSandbox.RootDir()) + gitURL := uri.File(repoSandbox.RootDir().String()) source := newSource(t, gitURL, "main") - got := download.Vendor(t.TempDir(), project.NewPath("/vendor"), source, nil) + got := download.Vendor(test.TempDir(t), project.NewPath("/vendor"), source, nil) assert.EqualInts(t, 0, len(got.Vendored), "vendored should be empty") assert.EqualInts(t, 1, len(got.Ignored), "should have single ignored") assert.IsError(t, got.Ignored[0].Reason, errors.E(hcl.ErrHCLSyntax)) } -func listFiles(t *testing.T, dir string) []string { +func listFiles(t *testing.T, dir os.Path) []string { t.Helper() - entries, err := os.ReadDir(dir) + entries, err := stdos.ReadDir(dir.String()) assert.NoError(t, err) files := []string{} - dirs := []string{} + dirs := os.Paths{} for _, entry := range entries { - path := filepath.Join(dir, entry.Name()) + path := dir.Join(entry.Name()) if entry.IsDir() { dirs = append(dirs, path) } else { - files = append(files, path) + files = append(files, path.String()) } } diff --git a/modvendor/manifest/manifest.go b/modvendor/manifest/manifest.go index 602ac0ef2d..f7fb297bbd 100644 --- a/modvendor/manifest/manifest.go +++ b/modvendor/manifest/manifest.go @@ -5,18 +5,18 @@ package manifest import ( - "os" - "path/filepath" + stdos "os" "github.com/go-git/go-git/v5/plumbing/format/gitignore" "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/hcl" + "github.com/terramate-io/terramate/os" ) // LoadFileMatcher will load a gitignore.Matcher -func LoadFileMatcher(rootdir string) (gitignore.Matcher, error) { - dotTerramate := filepath.Join(rootdir, ".terramate") - dotTerramateInfo, err := os.Stat(dotTerramate) +func LoadFileMatcher(rootdir os.Path) (gitignore.Matcher, error) { + dotTerramate := rootdir.Join(".terramate") + dotTerramateInfo, err := stdos.Stat(dotTerramate.String()) if err == nil && dotTerramateInfo.IsDir() { cfg, err := hcl.ParseDir(rootdir, dotTerramate) diff --git a/modvendor/modvendor.go b/modvendor/modvendor.go index bdcd115196..70775fad89 100644 --- a/modvendor/modvendor.go +++ b/modvendor/modvendor.go @@ -8,6 +8,7 @@ package modvendor import ( "path/filepath" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/tf" ) @@ -22,11 +23,11 @@ func TargetDir(vendorDir project.Path, modsrc tf.Source) project.Path { } // SourceDir returns the source directory from a target directory (installed module). -func SourceDir(path string, rootdir string, vendordir project.Path) string { +func SourceDir(path os.Path, rootdir os.Path, vendordir project.Path) string { return sourceDir(path, rootdir, vendordir) } // AbsVendorDir returns the absolute host path of the vendored module source. -func AbsVendorDir(rootdir string, vendorDir project.Path, modsrc tf.Source) string { - return filepath.Join(rootdir, filepath.FromSlash(TargetDir(vendorDir, modsrc).String())) +func AbsVendorDir(rootdir os.Path, vendorDir project.Path, modsrc tf.Source) os.Path { + return rootdir.Join(filepath.FromSlash(TargetDir(vendorDir, modsrc).String())) } diff --git a/modvendor/modvendor_common.go b/modvendor/modvendor_common.go index bd564a45b2..479fa70418 100644 --- a/modvendor/modvendor_common.go +++ b/modvendor/modvendor_common.go @@ -7,9 +7,8 @@ package modvendor import ( "path" - "path/filepath" - "strings" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/tf" ) @@ -20,6 +19,6 @@ func targetPathDir(vendorDir project.Path, modsrc tf.Source) project.Path { ) } -func sourceDir(path string, rootdir string, vendordir project.Path) string { - return strings.TrimPrefix(path, filepath.Join(rootdir, vendordir.String())) +func sourceDir(path os.Path, rootdir os.Path, vendordir project.Path) string { + return path.TrimPrefix(rootdir.Join(vendordir.String())) } diff --git a/os/_test_mock.tf b/os/_test_mock.tf new file mode 100644 index 0000000000..a7392736da --- /dev/null +++ b/os/_test_mock.tf @@ -0,0 +1,14 @@ +// TERRAMATE: GENERATED AUTOMATICALLY DO NOT EDIT + +resource "local_file" "os" { + content = <<-EOT +package os // import "github.com/terramate-io/terramate/os" + +type Path string + func HostPath(wd Path, p string) Path + func NewHostPath(p string) Path +type Paths []Path +EOT + + filename = "${path.module}/mock-os.ignore" +} diff --git a/os/hostpath.go b/os/hostpath.go new file mode 100644 index 0000000000..90d2441250 --- /dev/null +++ b/os/hostpath.go @@ -0,0 +1,47 @@ +package os + +import ( + "fmt" + "path/filepath" + "strings" +) + +type Path string +type Paths []Path + +func NewHostPath(p string) Path { + if !filepath.IsAbs(p) { + panic(fmt.Sprintf("bug: expected an absolute host path: %s", p)) + } + return Path(p) +} + +func HostPath(wd Path, p string) Path { + if !filepath.IsAbs(p) { + return Path(filepath.Join(wd.String(), p)) + } + return Path(p) +} + +func newpath(p string) Path { return Path(p) } + +func (p Path) Dir() Path { return newpath(filepath.Dir(p.String())) } + +func (p Path) Base() string { return filepath.Base(p.String()) } + +func (p Path) Join(strs ...string) Path { + parts := make([]string, len(strs)+1) + parts[0] = p.String() + for i, str := range strs { + parts[1+i] = filepath.FromSlash(str) + } + return newpath(filepath.Join(parts...)) +} + +func (p Path) String() string { return string(p) } + +func (p Path) HasPrefix(str string) bool { return strings.HasPrefix(p.String(), str) } + +func (p Path) TrimPrefix(another Path) string { + return strings.TrimPrefix(p.String(), another.String()) +} diff --git a/os/stack.tm.hcl b/os/stack.tm.hcl new file mode 100644 index 0000000000..ba8a0f1d26 --- /dev/null +++ b/os/stack.tm.hcl @@ -0,0 +1,6 @@ +stack { + name = "package os // import \"github.com/terramate-io/terramate/os\"" + description = "package os // import \"github.com/terramate-io/terramate/os\"\n\ntype Path string\n func HostPath(wd Path, p string) Path\n func NewHostPath(p string) Path\ntype Paths []Path" + tags = ["golang", "os"] + id = "d07824d6-10a3-4f98-967a-ca2149cfb534" +} diff --git a/project/project.go b/project/project.go index 1e4ce7299c..ff807660f7 100644 --- a/project/project.go +++ b/project/project.go @@ -6,17 +6,19 @@ package project import ( "errors" "fmt" - "path" + stdpath "path" "path/filepath" "sort" "strconv" "strings" + "github.com/terramate-io/terramate/os" + "github.com/zclconf/go-cty/cty" ) // Path is a project path. -// The project paths are always absolute forward slashed paths with no lexical +// The project paths can be either empty or an absolute forward slashed path with no lexical // processing left, which means they must be cleaned paths. See: // // https://pkg.go.dev/path#Clean @@ -36,28 +38,39 @@ type Runtime map[string]cty.Value // TODO(i4k): get rid of this limit. const MaxGlobalLabels = 256 +var empty = Path{} + // NewPath creates a new project path. // It panics if a relative path is provided. func NewPath(p string) Path { - if !path.IsAbs(p) { + if p == "" { + return empty + } + if !stdpath.IsAbs(p) { panic(fmt.Errorf("project path must be absolute but got %q", p)) } + return newpath(p) +} + +// assume the path is valid. +func newpath(p string) Path { return Path{ - path: path.Clean(p), + path: stdpath.Clean(p), } } // Dir returns the path's directory. func (p Path) Dir() Path { + if p.path == "" { + return p + } return Path{ - path: path.Dir(p.String()), + path: stdpath.Dir(p.String()), } } // HostPath computes the absolute host path from the provided rootdir. -func (p Path) HostPath(rootdir string) string { - return filepath.Join(rootdir, filepath.FromSlash(p.path)) -} +func (p Path) HostPath(rootdir os.Path) os.Path { return os.HostPath(rootdir, p.String()) } // HasPrefix tests whether p begins with s prefix. func (p Path) HasPrefix(s string) bool { @@ -72,10 +85,10 @@ func (p Path) HasDirPrefix(s string) bool { return s == p.String() || strings.HasPrefix(p.String(), s+"/") } -// Join joins the pathstr path into p. See [path.Join] for the underlying +// Join joins the pathstr path into p. See [stdpath.Join] for the underlying // implementation. func (p Path) Join(pathstr string) Path { - return NewPath(path.Join(p.String(), pathstr)) + return NewPath(stdpath.Join(p.String(), pathstr)) } // String returns the path as a string. @@ -92,7 +105,7 @@ func (p *Path) UnmarshalJSON(data []byte) error { if err != nil { return err } - if !path.IsAbs(str) { + if !stdpath.IsAbs(str) { return errors.New(`a project path must start with "/"`) } p2 := NewPath(str) @@ -118,26 +131,18 @@ func (paths Paths) Sort() { // PrjAbsPath converts the file system absolute path absdir into an absolute // project path on the form /path/on/project relative to the given root. -func PrjAbsPath(root, abspath string) Path { - d := filepath.ToSlash(strings.TrimPrefix(abspath, root)) - if d == "" { - d = "/" - } - if d[0] != '/' { - // handle root=/ abspath=/file +func PrjAbsPath(root, abspath os.Path) Path { + d := filepath.ToSlash(abspath.TrimPrefix(root)) + if d == "" || d[0] != '/' { + // handles: root=/ abspath=/ + // root=/ abspath=/file d = "/" + d } - return NewPath(d) -} - -// AbsPath takes the root project dir and a project's absolute path prjAbsPath -// and returns an absolute path to the file system. -func AbsPath(root, prjAbsPath string) string { - return filepath.Join(root, prjAbsPath) + return newpath(d) } // FriendlyFmtDir formats the directory in a friendly way for tooling output. -func FriendlyFmtDir(root, wd, dir string) (string, bool) { +func FriendlyFmtDir(root, wd os.Path, dir string) (string, bool) { trimPart := PrjAbsPath(root, wd).String() if !strings.HasPrefix(dir, trimPart) { return "", false diff --git a/run/env_test.go b/run/env_test.go index f26d4660be..598b0380b4 100644 --- a/run/env_test.go +++ b/run/env_test.go @@ -5,7 +5,6 @@ package run_test import ( "fmt" - "path/filepath" "testing" "github.com/madlambda/spells/assert" @@ -571,7 +570,7 @@ func TestLoadRunEnv(t *testing.T) { s := sandbox.NoGit(t, true) s.BuildTree(tcase.layout) for _, cfg := range tcase.configs { - path := filepath.Join(s.RootDir(), cfg.path) + path := s.RootDir().Join(cfg.path) fname := cfg.fname if fname == "" { fname = "run_env_test_cfg.tm" diff --git a/run/order.go b/run/order.go index 196c83a947..038a9b8a38 100644 --- a/run/order.go +++ b/run/order.go @@ -6,14 +6,14 @@ package run import ( "cmp" "fmt" - "os" + stdos "os" "path" - "path/filepath" "strings" "github.com/rs/zerolog/log" "github.com/terramate-io/terramate/config" "github.com/terramate-io/terramate/errors" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/printer" "github.com/terramate-io/terramate/run/dag" "golang.org/x/exp/slices" @@ -103,7 +103,7 @@ func buildValidStackDAG[S ~[]E, E any]( logger := log.With(). Str("action", "run.buildOrderedStackDAG()"). - Str("root", root.HostDir()). + Stringer("root", root.Path()). Logger() isParentStack := func(s1, s2 *config.Stack) bool { @@ -208,13 +208,13 @@ func BuildDAG( continue } - var abspath string + var abspath os.Path if path.IsAbs(pathstr) { - abspath = filepath.Join(root.HostDir(), filepath.FromSlash(pathstr)) + abspath = root.Path().Join(pathstr) } else { - abspath = filepath.Join(s.HostDir(root), filepath.FromSlash(pathstr)) + abspath = s.HostDir(root).Join(pathstr) } - st, err := os.Stat(abspath) + st, err := stdos.Stat(abspath.String()) if err != nil { logger.Debug().Str("path", pathstr).Err(err).Msgf("Invalid stack.%s path", fieldname) printer.Stderr.Warn( @@ -274,7 +274,7 @@ func BuildDAG( for _, elem := range stacks { logger = log.With(). Str("action", "run.BuildDAG()"). - Str("path", root.HostDir()). + Stringer("path", root.Path()). Stringer("stack", elem.Dir()). Logger() diff --git a/stack/clone.go b/stack/clone.go index 3ffd8a9e78..7d0e26192a 100644 --- a/stack/clone.go +++ b/stack/clone.go @@ -4,7 +4,7 @@ package stack import ( - "os" + stdos "os" "path/filepath" "strings" @@ -16,6 +16,7 @@ import ( "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/fs" "github.com/terramate-io/terramate/hcl" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/zclconf/go-cty/cty" ) @@ -34,26 +35,26 @@ const ( // - All files and directories are copied (except dotfiles/dirs) // - If cloned stack has an ID it will be adjusted to a generated UUID. // - If cloned stack has no ID the cloned stack also won't have an ID. -func Clone(root *config.Root, destdir, srcdir string, skipChildStacks bool) (int, error) { - rootdir := root.HostDir() +func Clone(root *config.Root, destdir, srcdir os.Path, skipChildStacks bool) (int, error) { + rootdir := root.Path() logger := log.With(). Str("action", "stack.Clone()"). - Str("rootdir", rootdir). - Str("destdir", destdir). - Str("srcdir", srcdir). + Stringer("rootdir", rootdir). + Stringer("destdir", destdir). + Stringer("srcdir", srcdir). Bool("skipChildStacks", skipChildStacks). Logger() - if !strings.HasPrefix(srcdir, rootdir) { + if !srcdir.HasPrefix(rootdir.String()) { return 0, errors.E(ErrInvalidStackDir, "src dir %q must be inside project root %q", srcdir, rootdir) } - if !strings.HasPrefix(destdir, rootdir) { + if !destdir.HasPrefix(rootdir.String()) { return 0, errors.E(ErrInvalidStackDir, "dest dir %q must be inside project root %q", destdir, rootdir) } - if _, err := os.Stat(destdir); err == nil { + if _, err := stdos.Stat(destdir.String()); err == nil { return 0, errors.E(ErrCloneDestDirExists, destdir) } @@ -63,7 +64,7 @@ func Clone(root *config.Root, destdir, srcdir string, skipChildStacks bool) (int return } - if err := os.RemoveAll(destdir); err != nil { + if err := stdos.RemoveAll(destdir.String()); err != nil { logger.Debug().Err(err).Msg("failed to cleanup destdir after error") } }() @@ -82,20 +83,20 @@ func Clone(root *config.Root, destdir, srcdir string, skipChildStacks bool) (int } type cloneTask struct { - Srcdir string - Destdir string + Srcdir os.Path + Destdir os.Path ShouldUpdateID bool } tasks := []cloneTask{} // Use this set to identify stack root dirs and don't recurse into them when copying - stackset := map[string]struct{}{} + stackset := map[os.Path]struct{}{} for _, e := range stackTrees { - stackSrcdir := e.HostDir() - rel, _ := filepath.Rel(srcdir, stackSrcdir) + stackSrcdir := e.Path() + rel, _ := filepath.Rel(srcdir.String(), stackSrcdir.String()) - stackDestdir := filepath.Join(destdir, rel) + stackDestdir := destdir.Join(rel) // If destdir is within srcdir, we could encounter a stackDestdir in the source dir // created by a previous cloneTask. They must be ignored, too. @@ -118,12 +119,12 @@ func Clone(root *config.Root, destdir, srcdir string, skipChildStacks bool) (int } for _, st := range tasks { - filter := func(dir string, entry os.DirEntry) bool { + filter := func(dir os.Path, entry stdos.DirEntry) bool { if strings.HasPrefix(entry.Name(), ".") { return false } - abspath := filepath.Join(dir, entry.Name()) + abspath := dir.Join(entry.Name()) _, found := stackset[abspath] return !found } @@ -148,8 +149,8 @@ func Clone(root *config.Root, destdir, srcdir string, skipChildStacks bool) (int // UpdateStackID updates the stack.id of the given stack directory. // The functions updates just the file which defines the stack block. // The updated file will lose all comments. -func UpdateStackID(root *config.Root, stackdir string) (string, error) { - parser, err := hcl.NewTerramateParser(root.HostDir(), stackdir) +func UpdateStackID(root *config.Root, stackdir os.Path) (string, error) { + parser, err := hcl.NewTerramateParser(root.Path(), stackdir) if err != nil { return "", err } @@ -162,12 +163,12 @@ func UpdateStackID(root *config.Root, stackdir string) (string, error) { return "", err } - stackFilePath := getStackFilepath(parser) - if stackFilePath == "" { + stackFilePath, ok := getStackFilepath(parser) + if !ok { return "", errors.E("stack does not have a stack block") } - st, err := os.Lstat(stackFilePath) + st, err := stdos.Lstat(stackFilePath.String()) if err != nil { return "", errors.E(err, "stating the stack file") } @@ -178,12 +179,12 @@ func UpdateStackID(root *config.Root, stackdir string) (string, error) { // has no comments on it, so building a new HCL file from the parsed // AST will lose all comments from the original code. - stackContents, err := os.ReadFile(stackFilePath) + stackContents, err := stdos.ReadFile(stackFilePath.String()) if err != nil { return "", errors.E(err, "reading stack definition file") } - parsed, diags := hclwrite.ParseConfig([]byte(stackContents), stackFilePath, hhcl.InitialPos) + parsed, diags := hclwrite.ParseConfig([]byte(stackContents), stackFilePath.String(), hhcl.InitialPos) if diags.HasErrors() { return "", errors.E(diags, "parsing stack configuration") } @@ -205,7 +206,7 @@ func UpdateStackID(root *config.Root, stackdir string) (string, error) { body := block.Body() body.SetAttributeValue("id", cty.StringVal(id)) - err = os.WriteFile(stackFilePath, parsed.Bytes(), originalFileMode) + err = stdos.WriteFile(stackFilePath.String(), parsed.Bytes(), originalFileMode) if err != nil { return "", err } @@ -215,13 +216,13 @@ func UpdateStackID(root *config.Root, stackdir string) (string, error) { return "", errors.E("stack block not found") } -func getStackFilepath(parser *hcl.TerramateParser) string { +func getStackFilepath(parser *hcl.TerramateParser) (os.Path, bool) { for filepath, body := range parser.ParsedBodies() { for _, block := range body.Blocks { if block.Type == hcl.StackBlockType { - return filepath + return filepath, true } } } - return "" + return "", false } diff --git a/stack/clone_test.go b/stack/clone_test.go index c97c822c12..46d2682ec6 100644 --- a/stack/clone_test.go +++ b/stack/clone_test.go @@ -6,7 +6,6 @@ package stack_test import ( "fmt" "os" - "path/filepath" "testing" "github.com/madlambda/spells/assert" @@ -143,8 +142,8 @@ func TestStackClone(t *testing.T) { s := sandbox.NoGit(t, true) s.BuildTree(tc.layout) - srcdir := filepath.Join(s.RootDir(), tc.src) - destdir := filepath.Join(s.RootDir(), tc.dest) + srcdir := s.RootDir().Join(tc.src) + destdir := s.RootDir().Join(tc.dest) _, err := stack.Clone(s.Config(), destdir, srcdir, false) assert.IsError(t, err, tc.wantErr) @@ -161,7 +160,7 @@ func TestStackCloneSrcDirMustBeInsideRootdir(t *testing.T) { t.Parallel() s := sandbox.NoGit(t, true) srcdir := test.TempDir(t) - destdir := filepath.Join(s.RootDir(), "new-stack") + destdir := s.RootDir().Join("new-stack") _, err := stack.Clone(s.Config(), destdir, srcdir, false) assert.IsError(t, err, errors.E(stack.ErrInvalidStackDir)) } @@ -169,7 +168,7 @@ func TestStackCloneSrcDirMustBeInsideRootdir(t *testing.T) { func TestStackCloneTargetDirMustBeInsideRootdir(t *testing.T) { t.Parallel() s := sandbox.NoGit(t, true) - srcdir := filepath.Join(s.RootDir(), "src-stack") + srcdir := s.RootDir().Join("src-stack") destdir := test.TempDir(t) _, err := stack.Clone(s.Config(), destdir, srcdir, false) assert.IsError(t, err, errors.E(stack.ErrInvalidStackDir)) @@ -183,8 +182,8 @@ func TestStackCloneIgnoresDotDirsAndFiles(t *testing.T) { "f:stack/.dotfile", "f:stack/.dotdir/file", }) - srcdir := filepath.Join(s.RootDir(), "stack") - destdir := filepath.Join(s.RootDir(), "cloned-stack") + srcdir := s.RootDir().Join("stack") + destdir := s.RootDir().Join("cloned-stack") _, err := stack.Clone(s.Config(), destdir, srcdir, false) assert.NoError(t, err) @@ -240,8 +239,8 @@ generate_hcl "test2.hcl" { stackEntry.CreateFile(stackCfgFilename, fmt.Sprintf(stackCfgTemplate, stackID, stackName, stackDesc)) - srcdir := filepath.Join(s.RootDir(), "stack") - destdir := filepath.Join(s.RootDir(), "cloned-stack") + srcdir := s.RootDir().Join("stack") + destdir := s.RootDir().Join("cloned-stack") _, err := stack.Clone(s.Config(), destdir, srcdir, false) assert.NoError(t, err) @@ -294,8 +293,8 @@ stack { stackEntry.CreateFile(stackCfgFilename, fmt.Sprintf(stackCfgTemplate, stackName, stackDesc)) - srcdir := filepath.Join(s.RootDir(), "stack") - destdir := filepath.Join(s.RootDir(), "cloned-stack") + srcdir := s.RootDir().Join("stack") + destdir := s.RootDir().Join("cloned-stack") _, err := stack.Clone(s.Config(), destdir, srcdir, false) assert.NoError(t, err) diff --git a/stack/create.go b/stack/create.go index 2f2918a9b7..98431c2d47 100644 --- a/stack/create.go +++ b/stack/create.go @@ -7,7 +7,6 @@ import ( "fmt" "os" "path" - "path/filepath" "strings" "github.com/terramate-io/terramate/config" @@ -54,12 +53,12 @@ func Create(root *config.Root, stack config.Stack, imports ...string) (err error return errors.E(ErrStackAlreadyExists) } - hostpath := stack.Dir.HostPath(root.HostDir()) - if err := os.MkdirAll(hostpath, createDirMode); err != nil { + hostpath := stack.Dir.HostPath(root.Path()) + if err := os.MkdirAll(hostpath.String(), createDirMode); err != nil { return errors.E(err, "failed to create new stack directories") } - _, err = os.Stat(filepath.Join(hostpath, DefaultFilename)) + _, err = os.Stat(hostpath.Join(DefaultFilename).String()) if err == nil { // Even if there is no stack block inside the file, we can't overwrite // the user file anyway. @@ -85,7 +84,7 @@ func Create(root *config.Root, stack config.Stack, imports ...string) (err error tmCfg.Stack = &stackCfg - stackFile, err := os.Create(filepath.Join(hostpath, DefaultFilename)) + stackFile, err := os.Create(hostpath.Join(DefaultFilename).String()) if err != nil { return errors.E(err, "creating/truncating stack file") } diff --git a/stack/create_test.go b/stack/create_test.go index 3a78f81a89..b50a4a95ae 100644 --- a/stack/create_test.go +++ b/stack/create_test.go @@ -5,13 +5,13 @@ package stack_test import ( "path" - "path/filepath" "testing" "github.com/madlambda/spells/assert" "github.com/terramate-io/terramate/config" "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/hcl" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/stack" "github.com/terramate-io/terramate/test" @@ -199,11 +199,11 @@ func TestStackCreation(t *testing.T) { } } -func buildImportedFiles(t *testing.T, rootdir string, imports []string) { +func buildImportedFiles(t *testing.T, rootdir os.Path, imports []string) { t.Helper() for _, importPath := range imports { - abspath := filepath.Join(rootdir, importPath) - test.WriteFile(t, filepath.Dir(abspath), filepath.Base(abspath), "") + abspath := rootdir.Join(importPath) + test.WriteFile(t, abspath.Dir(), abspath.Base(), "") } } diff --git a/stack/manager.go b/stack/manager.go index 026c35f782..08d42c5028 100644 --- a/stack/manager.go +++ b/stack/manager.go @@ -6,15 +6,15 @@ package stack import ( "fmt" "io/fs" - "os" + stdos "os" "path" - "path/filepath" "sort" "github.com/rs/zerolog/log" "github.com/terramate-io/terramate/config" "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/git" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/printer" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/run" @@ -131,7 +131,7 @@ func (m *Manager) ListChanged(gitBaseRef string) (*Report, error) { return nil, errors.E( errListChanged, "the path \"%s\" is not a git repository", - m.root.HostDir(), + m.root.Path(), ) } @@ -140,7 +140,7 @@ func (m *Manager) ListChanged(gitBaseRef string) (*Report, error) { return nil, errors.E(errListChanged, err) } - changedFiles, err := m.listChangedFiles(m.root.HostDir(), gitBaseRef) + changedFiles, err := m.listChangedFiles(m.root.Path(), gitBaseRef) if err != nil { return nil, errors.E(errListChanged, err) } @@ -149,8 +149,8 @@ func (m *Manager) ListChanged(gitBaseRef string) (*Report, error) { ignoreSet := map[project.Path]struct{}{} for _, path := range changedFiles { - abspath := filepath.Join(m.root.HostDir(), path) - projpath := project.PrjAbsPath(m.root.HostDir(), abspath) + abspath := m.root.Path().Join(path) + projpath := project.PrjAbsPath(m.root.Path(), abspath) logger = logger.With(). Stringer("path", projpath). @@ -162,7 +162,7 @@ func (m *Manager) ListChanged(gitBaseRef string) (*Report, error) { Stringer("trigger", triggeredStack). Logger() - if _, err := os.Stat(abspath); err != nil { + if _, err := stdos.Stat(abspath.String()); err != nil { if errors.Is(err, fs.ErrNotExist) { logger.Debug().Msg("ignoring deleted trigger file") continue @@ -195,7 +195,7 @@ func (m *Manager) ListChanged(gitBaseRef string) (*Report, error) { continue } - s, err := config.NewStackFromHCL(m.root.HostDir(), cfg.Node) + s, err := config.NewStackFromHCL(m.root.Path(), cfg.Node) if err != nil { return nil, errors.E(errListChanged, err) } @@ -207,13 +207,12 @@ func (m *Manager) ListChanged(gitBaseRef string) (*Report, error) { continue } - dirname := filepath.Dir(abspath) - - if _, ok := stackSet[project.PrjAbsPath(m.root.HostDir(), dirname)]; ok { + dirname := abspath.Dir() + if _, ok := stackSet[project.PrjAbsPath(m.root.Path(), dirname)]; ok { continue } - cfgpath := project.PrjAbsPath(m.root.HostDir(), dirname) + cfgpath := project.PrjAbsPath(m.root.Path(), dirname) stackTree, found := m.root.Lookup(cfgpath) if !found || !stackTree.IsStack() { checkdir := cfgpath @@ -230,7 +229,7 @@ func (m *Manager) ListChanged(gitBaseRef string) (*Report, error) { } } - s, err := config.NewStackFromHCL(m.root.HostDir(), stackTree.Node) + s, err := config.NewStackFromHCL(m.root.Path(), stackTree.Node) if err != nil { return nil, errors.E(errListChanged, err) } @@ -261,7 +260,7 @@ func (m *Manager) ListChanged(gitBaseRef string) (*Report, error) { if m.root.IsTerragruntChangeDetectionEnabled() { // discover Terragrunt modules - tgModules, err = tg.ScanModules(m.root.HostDir(), project.NewPath("/"), false) + tgModules, err = tg.ScanModules(m.root.Path(), project.NewPath("/"), false) if err != nil { return nil, errors.E(errListChanged, err, "scanning terragrunt modules") } @@ -301,8 +300,7 @@ rangeStacks: return nil } - tfpath := filepath.Join(stack.HostDir(m.root), file.Name()) - + tfpath := stack.HostDir(m.root).Join(file.Name()) modules, err := tf.ParseModules(tfpath) if err != nil { return errors.E(errListChanged, "parsing modules", err) @@ -317,7 +315,7 @@ rangeStacks: if changed { logger.Debug(). Stringer("stack", stack). - Str("configFile", tfpath). + Stringer("configFile", tfpath). Msg("Module changed.") stack.IsChanged = true @@ -466,8 +464,8 @@ func (m *Manager) AddWantedOf(scopeStacks config.List[*config.SortableStack]) (c return selectedStacks, nil } -func (m *Manager) filesApply(dir string, apply func(file fs.DirEntry) error) (err error) { - f, err := os.Open(dir) +func (m *Manager) filesApply(dir os.Path, apply func(file fs.DirEntry) error) (err error) { + f, err := stdos.Open(dir.String()) if err != nil { return errors.E(err, "opening directory %q", dir) } @@ -500,7 +498,7 @@ func (m *Manager) filesApply(dir string, apply func(file fs.DirEntry) error) (er // called recursively. The visited keep track of the modules already parsed to // avoid infinite loops. func (m *Manager) tfModuleChanged( - mod tf.Module, basedir string, gitBaseRef string, visited map[string]bool, + mod tf.Module, basedir os.Path, gitBaseRef string, visited map[string]bool, ) (changed bool, why string, err error) { if _, ok := visited[mod.Source]; ok { return false, "", nil @@ -512,9 +510,8 @@ func (m *Manager) tfModuleChanged( return false, "", nil } - modPath := filepath.Join(basedir, mod.Source) - - st, err := os.Stat(modPath) + modPath := basedir.Join(mod.Source) + st, err := stdos.Stat(modPath.String()) // TODO(i4k): resolve symlinks @@ -543,7 +540,7 @@ func (m *Manager) tfModuleChanged( return nil } - modules, err := tf.ParseModules(filepath.Join(modPath, file.Name())) + modules, err := tf.ParseModules(modPath.Join(file.Name())) if err != nil { return errors.E(err, "parsing module %q", mod.Source) } @@ -577,7 +574,7 @@ func (m *Manager) tgModuleChanged( ) (changed bool, why string, err error) { tfMod := tf.Module{Source: tgMod.Source} if tfMod.IsLocal() { - changed, why, err := m.tfModuleChanged(tfMod, project.AbsPath(m.root.HostDir(), tgMod.Path.String()), gitBaseRef, make(map[string]bool)) + changed, why, err := m.tfModuleChanged(tfMod, tgMod.Path.HostPath(m.root.Path()), gitBaseRef, make(map[string]bool)) if err != nil { return false, "", errors.E(errListChanged, err, "checking if Terraform module changes (in Terragrunt context)") } @@ -597,15 +594,15 @@ func (m *Manager) tgModuleChanged( } for _, changedFile := range changedFiles { - changedPath := project.PrjAbsPath(m.root.HostDir(), changedFile) + changedPath := project.NewPath("/" + changedFile) if dep == changedPath { return true, fmt.Sprintf("module %q changed because %q changed", tgMod.Path, dep), nil } } - depAbsPath := project.AbsPath(m.root.HostDir(), dep.String()) + depAbsPath := dep.HostPath(m.root.Path()) // if the dep is a directory, check if it changed - info, err := os.Lstat(depAbsPath) + info, err := stdos.Lstat(depAbsPath.String()) if err != nil { if errors.Is(err, fs.ErrNotExist) { continue @@ -642,8 +639,8 @@ func (m *Manager) tgModuleChanged( } // listChangedFiles lists all changed files in the dir directory. -func (m *Manager) listChangedFiles(dir string, gitBaseRef string) ([]string, error) { - st, err := os.Stat(dir) +func (m *Manager) listChangedFiles(dir os.Path, gitBaseRef string) ([]string, error) { + st, err := stdos.Stat(dir.String()) if err != nil { return nil, errors.E(err, "stat failed on %q", dir) } diff --git a/stack/manager_test.go b/stack/manager_test.go index 1b32575fe7..f44744ffb9 100644 --- a/stack/manager_test.go +++ b/stack/manager_test.go @@ -5,12 +5,12 @@ package stack_test import ( "fmt" - "path/filepath" "strings" "testing" "github.com/madlambda/spells/assert" "github.com/terramate-io/terramate/config" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/stack" "github.com/terramate-io/terramate/test" @@ -18,8 +18,8 @@ import ( ) type repository struct { - Dir string - modules []string + Dir os.Path + modules os.Paths } type listTestResult struct { @@ -359,7 +359,7 @@ func singleChangedDotDirStackRepo(t *testing.T) repository { assert.NoError(t, g.Checkout("testbranch2", true), "git checkout failed") - _ = test.WriteFile(t, filepath.Join(repo.Dir, ".bar"), "bar", "bar") + _ = test.WriteFile(t, repo.Dir.Join(".bar"), "bar", "bar") assert.NoError(t, g.Add(".bar"), "add .bar dir failed") assert.NoError(t, g.Commit("bar dir message"), "bar commit failed") @@ -382,7 +382,7 @@ func singleNotChangedStack(t *testing.T) repository { // add a second commit to be able to test gitBaseRef=HEAD^ readmePath := test.WriteFile(t, repo, "Something", "test") - assert.NoError(t, g.Add(readmePath), "add terramate file failed") + assert.NoError(t, g.Add(readmePath.String()), "add terramate file failed") assert.NoError(t, g.Commit("add Something message"), "commit failed") assert.NoError(t, g.Push("origin", "main"), "push to origin") @@ -400,7 +400,7 @@ module "empty" { } `) - assert.NoError(t, g.Add(repo.Dir), "add files") + assert.NoError(t, g.Add(repo.Dir.String()), "add files") assert.NoError(t, g.Commit("files"), "commit files") assert.NoError(t, g.Push("origin", "main"), "push to origin") return repo @@ -418,7 +418,7 @@ func singleNotChangedStackNewBranch(t *testing.T) repository { return repo } -func addMergeCommit(t *testing.T, repodir, branch string) { +func addMergeCommit(t *testing.T, repodir os.Path, branch string) { g := test.NewGitWrapper(t, repodir, []string{}) assert.NoError(t, g.Checkout("main", false), "checkout main failed") @@ -488,14 +488,14 @@ func multipleStacksOneChangedRepo(t *testing.T) repository { assert.NoError(t, g.Checkout("testbranch", true), "create branch failed") - otherStack := filepath.Join(repo.Dir, "not-changed-stack") - test.MkdirAll(t, otherStack) + otherStack := repo.Dir.Join("not-changed-stack") + test.MkdirAll(t, otherStack.String()) root, err := config.LoadRoot(repo.Dir) assert.NoError(t, err) createStack(t, root, otherStack) - assert.NoError(t, g.Add(filepath.Join(otherStack, stack.DefaultFilename)), + assert.NoError(t, g.Add(otherStack.Join(stack.DefaultFilename).String()), "git add otherstack failed") assert.NoError(t, g.Commit("other stack message"), "commit failed") @@ -505,14 +505,14 @@ func multipleStacksOneChangedRepo(t *testing.T) repository { // not merged changes assert.NoError(t, g.Checkout("testbranch2", true), "create branch testbranch2 failed") - otherStack = filepath.Join(repo.Dir, "changed-stack") - test.MkdirAll(t, otherStack) + otherStack = repo.Dir.Join("changed-stack") + test.MkdirAll(t, otherStack.String()) root, err = config.LoadRoot(repo.Dir) assert.NoError(t, err) createStack(t, root, otherStack) - assert.NoError(t, g.Add(filepath.Join(otherStack, stack.DefaultFilename)), + assert.NoError(t, g.Add(otherStack.Join(stack.DefaultFilename).String()), "git add otherstack failed") assert.NoError(t, g.Commit("other stack message"), "commit failed") return repo @@ -527,11 +527,11 @@ func multipleChangedStacksRepo(t *testing.T) repository { assert.NoError(t, err) for i := 0; i < 3; i++ { - otherStack := filepath.Join(repo.Dir, "changed-stack-"+fmt.Sprint(i)) - test.MkdirAll(t, otherStack) + otherStack := repo.Dir.Join("changed-stack-" + fmt.Sprint(i)) + test.MkdirAll(t, otherStack.String()) createStack(t, root, otherStack) - assert.NoError(t, g.Add(filepath.Join(otherStack, stack.DefaultFilename)), + assert.NoError(t, g.Add(otherStack.Join(stack.DefaultFilename).String()), "git add otherstack failed") assert.NoError(t, g.Commit("other stack message"), "commit failed") } @@ -560,7 +560,7 @@ module "something" { } `) - assert.NoError(t, g.Add(repo.Dir), "add files") + assert.NoError(t, g.Add(repo.Dir.String()), "add files") assert.NoError(t, g.Commit("files"), "commit files") return repo @@ -573,22 +573,22 @@ func multipleStackOneChangedModule(t *testing.T) repository { assert.NoError(t, g.Checkout("testbranch", true), "create branch failed") - otherStack := filepath.Join(repo.Dir, "stack1") - test.MkdirAll(t, otherStack) + otherStack := repo.Dir.Join("stack1") + test.MkdirAll(t, otherStack.String()) root, err := config.LoadRoot(repo.Dir) assert.NoError(t, err) createStack(t, root, otherStack) - assert.NoError(t, g.Add(filepath.Join(otherStack, stack.DefaultFilename)), + assert.NoError(t, g.Add(otherStack.Join(stack.DefaultFilename).String()), "git add otherstack failed") assert.NoError(t, g.Commit("other stack message"), "commit failed") - otherStack = filepath.Join(repo.Dir, "stack2") - test.MkdirAll(t, otherStack) + otherStack = repo.Dir.Join("stack2") + test.MkdirAll(t, otherStack.String()) assert.NoError(t, err) createStack(t, root, otherStack) - assert.NoError(t, g.Add(filepath.Join(otherStack, stack.DefaultFilename)), + assert.NoError(t, g.Add(otherStack.Join(stack.DefaultFilename).String()), "git add otherstack failed") assert.NoError(t, g.Commit("other stack message"), "commit failed") @@ -601,20 +601,20 @@ module "something" { } `) - assert.NoError(t, g.Add(mainFile), "add main.tf") + assert.NoError(t, g.Add(mainFile.String()), "add main.tf") assert.NoError(t, g.Commit("add main.tf"), "commit main.tf") addMergeCommit(t, repo.Dir, "testbranch") assert.NoError(t, g.DeleteBranch("testbranch"), "delete temp branch") mainFile = test.WriteFile(t, module, "main.tf", "") - assert.NoError(t, g.Add(mainFile)) + assert.NoError(t, g.Add(mainFile.String())) assert.NoError(t, g.Commit("test")) assert.NoError(t, g.Push("origin", "main"), "push origin main") assert.NoError(t, g.Checkout("testbranch", true)) mainFile = test.WriteFile(t, module, "main.tf", "# comment") - assert.NoError(t, g.Add(mainFile)) + assert.NoError(t, g.Add(mainFile.String())) assert.NoError(t, g.Commit("test")) return repo @@ -641,15 +641,15 @@ module "something" { } `) - assert.NoError(t, g.Add(repo.Dir), "add files") + assert.NoError(t, g.Add(repo.Dir.String()), "add files") assert.NoError(t, g.Commit("files"), "commit files") readmeFile := test.WriteFile(t, module2, "README.md", "GENERATED BY TERRAMATE TESTS!") - assert.NoError(t, g.Add(readmeFile), "add readme file") + assert.NoError(t, g.Add(readmeFile.String()), "add readme file") assert.NoError(t, g.Commit("commit"), "commit readme") mainFile := test.WriteFile(t, module2, "main.tf", "") - assert.NoError(t, g.Add(mainFile), "add main.tf") + assert.NoError(t, g.Add(mainFile.String()), "add main.tf") assert.NoError(t, g.Commit("commit main.tf"), "commit main.tf") mainFile = test.WriteFile(t, module1, "main.tf", ` @@ -658,7 +658,7 @@ module "module2" { } `) - assert.NoError(t, g.Add(mainFile), "add main.tf") + assert.NoError(t, g.Add(mainFile.String()), "add main.tf") assert.NoError(t, g.Commit("commit main.tf"), "commit main.tf") assert.NoError(t, g.Push("origin", "main")) @@ -667,7 +667,7 @@ module "module2" { # file changed `) - assert.NoError(t, g.Add(mainFile), "add main.tf") + assert.NoError(t, g.Add(mainFile.String()), "add main.tf") assert.NoError(t, g.Commit("commit main.tf"), "commit main.tf") return repo @@ -703,7 +703,7 @@ func singleTerragruntStackWithNoChangesRepo(t *testing.T) repository { test.WriteFile(t, module1, "main.tf", `# empty file`) - assert.NoError(t, g.Add(repo.Dir), "add files") + assert.NoError(t, g.Add(repo.Dir.String()), "add files") assert.NoError(t, g.Commit("files"), "commit files") addMergeCommit(t, repo.Dir, "testbranch") @@ -748,7 +748,7 @@ func singleTerragruntStackWithSingleTerraformModuleChangedRepo(t *testing.T, ena test.WriteFile(t, module1, "main.tf", `# empty file`) - assert.NoError(t, g.Add(repo.Dir), "add files") + assert.NoError(t, g.Add(repo.Dir.String()), "add files") assert.NoError(t, g.Commit("files"), "commit files") addMergeCommit(t, repo.Dir, "testbranch") @@ -758,7 +758,7 @@ func singleTerragruntStackWithSingleTerraformModuleChangedRepo(t *testing.T, ena assert.NoError(t, g.Checkout("testbranch2", true), "create branch testbranch2 failed") test.WriteFile(t, module1, "main.tf", `# changed file`) - assert.NoError(t, g.Add(module1), "add files") + assert.NoError(t, g.Add(module1.String()), "add files") assert.NoError(t, g.Commit("module changed"), "commit files") return repo } @@ -799,7 +799,7 @@ func terragruntStackChangedDueToDependencyChangedRepo(t *testing.T) repository { g := test.NewGitWrapper(t, repo.Dir, []string{}) assert.NoError(t, g.Checkout("testbranch", true), "create branch failed") - assert.NoError(t, g.Add(repo.Dir), "add files") + assert.NoError(t, g.Add(repo.Dir.String()), "add files") assert.NoError(t, g.Commit("files"), "commit files") addMergeCommit(t, repo.Dir, "testbranch") @@ -812,7 +812,7 @@ func terragruntStackChangedDueToDependencyChangedRepo(t *testing.T) repository { Str("source", "github.com/test/test3"), ), ).String()) - assert.NoError(t, g.Add(anotherStack), "add files") + assert.NoError(t, g.Add(anotherStack.String()), "add files") assert.NoError(t, g.Commit("files"), "commit files") return repo @@ -866,7 +866,7 @@ func terragruntStackChangedDueToDepOfDepStacksChangedRepo(t *testing.T) reposito g := test.NewGitWrapper(t, repo.Dir, []string{}) assert.NoError(t, g.Checkout("testbranch", true), "create branch failed") - assert.NoError(t, g.Add(repo.Dir), "add files") + assert.NoError(t, g.Add(repo.Dir.String()), "add files") assert.NoError(t, g.Commit("files"), "commit files") addMergeCommit(t, repo.Dir, "testbranch") @@ -879,7 +879,7 @@ func terragruntStackChangedDueToDepOfDepStacksChangedRepo(t *testing.T) reposito Str("source", "changed/value"), ), ).String()) - assert.NoError(t, g.Add(depDepStack), "add files") + assert.NoError(t, g.Add(depDepStack.String()), "add files") assert.NoError(t, g.Commit("files"), "commit files") return repo @@ -933,7 +933,7 @@ func terragruntStackChangedDueToDepOfDepNonStacksChangedRepo(t *testing.T) repos g := test.NewGitWrapper(t, repo.Dir, []string{}) assert.NoError(t, g.Checkout("testbranch", true), "create branch failed") - assert.NoError(t, g.Add(repo.Dir), "add files") + assert.NoError(t, g.Add(repo.Dir.String()), "add files") assert.NoError(t, g.Commit("files"), "commit files") addMergeCommit(t, repo.Dir, "testbranch") @@ -946,7 +946,7 @@ func terragruntStackChangedDueToDepOfDepNonStacksChangedRepo(t *testing.T) repos Str("source", "changed/value"), ), ).String()) - assert.NoError(t, g.Add(depDepModule), "add files") + assert.NoError(t, g.Add(depDepModule.String()), "add files") assert.NoError(t, g.Commit("files"), "commit files") return repo @@ -1003,7 +1003,7 @@ func terragruntStackChangedDueToDepOfDepModuleSourceChangedRepo(t *testing.T) re g := test.NewGitWrapper(t, repo.Dir, []string{}) assert.NoError(t, g.Checkout("testbranch", true), "create branch failed") - assert.NoError(t, g.Add(repo.Dir), "add files") + assert.NoError(t, g.Add(repo.Dir.String()), "add files") assert.NoError(t, g.Commit("files"), "commit files") addMergeCommit(t, repo.Dir, "testbranch") @@ -1012,7 +1012,7 @@ func terragruntStackChangedDueToDepOfDepModuleSourceChangedRepo(t *testing.T) re // now we branch again and modify the dep-dep-tg-stack module assert.NoError(t, g.Checkout("testbranch2", true), "create branch testbranch2 failed") test.WriteFile(t, localModule, "main.tf", "# changed file") - assert.NoError(t, g.Add(localModule), "add files") + assert.NoError(t, g.Add(localModule.String()), "add files") assert.NoError(t, g.Commit("files"), "commit files") return repo @@ -1057,7 +1057,7 @@ func terragruntStackChangedDueToReferencedFileChangedRepo(t *testing.T) reposito g := test.NewGitWrapper(t, repo.Dir, []string{}) assert.NoError(t, g.Checkout("testbranch", true), "create branch failed") - assert.NoError(t, g.Add(repo.Dir), "add files") + assert.NoError(t, g.Add(repo.Dir.String()), "add files") assert.NoError(t, g.Commit("files"), "commit files") addMergeCommit(t, repo.Dir, "testbranch") @@ -1066,20 +1066,20 @@ func terragruntStackChangedDueToReferencedFileChangedRepo(t *testing.T) reposito // now we branch again and modify the common.tfvars file assert.NoError(t, g.Checkout("testbranch2", true), "create branch testbranch2 failed") test.WriteFile(t, repo.Dir, "common.tfvars", `key = "changed value"`) - assert.NoError(t, g.Add(repo.Dir), "add files") + assert.NoError(t, g.Add(repo.Dir.String()), "add files") assert.NoError(t, g.Commit("files"), "commit files") return repo } -func newManager(t *testing.T, basedir string) *stack.Manager { +func newManager(t *testing.T, basedir os.Path) *stack.Manager { root, err := config.LoadRoot(basedir) assert.NoError(t, err) g := test.NewGitWrapper(t, basedir, []string{}) return stack.NewGitAwareManager(root, g) } -func createStack(t *testing.T, root *config.Root, absdir string) { - dir := project.PrjAbsPath(root.HostDir(), absdir) +func createStack(t *testing.T, root *config.Root, absdir os.Path) { + dir := project.PrjAbsPath(root.Path(), absdir) assert.NoError(t, stack.Create(root, config.Stack{Dir: dir}), "terramate init failed") } diff --git a/stack/trigger/trigger.go b/stack/trigger/trigger.go index bbdd414660..1f3f973d0f 100644 --- a/stack/trigger/trigger.go +++ b/stack/trigger/trigger.go @@ -6,9 +6,8 @@ package trigger import ( "fmt" - "os" + stdos "os" "path" - "path/filepath" "strings" "time" @@ -20,6 +19,7 @@ import ( "github.com/terramate-io/terramate/config" "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/hcl/ast" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/zclconf/go-cty/cty" ) @@ -88,7 +88,7 @@ func Is(root *config.Root, filename project.Path) (info Info, stack project.Path if !exists { return Info{}, stack, false, nil } - info, err = ParseFile(filename.HostPath(root.HostDir())) + info, err = ParseFile(filename.HostPath(root.Path())) if err != nil { return Info{}, stack, true, err } @@ -96,9 +96,9 @@ func Is(root *config.Root, filename project.Path) (info Info, stack project.Path } // ParseFile will parse the given trigger file. -func ParseFile(path string) (Info, error) { +func ParseFile(path os.Path) (Info, error) { parser := hclparse.NewParser() - parsed, diags := parser.ParseHCLFile(path) + parsed, diags := parser.ParseHCLFile(path.String()) if diags.HasErrors() { return Info{}, errors.E(ErrParsing, diags) } @@ -218,9 +218,8 @@ func ParseFile(path string) (Info, error) { } // Dir will return the triggers directory for the project rooted at rootdir. -// Both rootdir and the returned value are host absolute paths. -func Dir(rootdir string) string { - return filepath.Join(rootdir, triggersDir) +func Dir(rootdir os.Path) os.Path { + return rootdir.Join(triggersDir) } func triggerFilename(kind Kind) (string, error) { @@ -242,13 +241,12 @@ func Create(root *config.Root, path project.Path, kind Kind, reason string) erro if err != nil { return errors.E(ErrTrigger, err) } - triggerDir := filepath.Join(root.HostDir(), triggersDir, path.String()) - if err := os.MkdirAll(triggerDir, 0775); err != nil { + triggerDir := Dir(root.Path()).Join(path.String()) + if err := stdos.MkdirAll(triggerDir.String(), 0775); err != nil { return errors.E(ErrTrigger, err, "creating trigger dir") } ctime := time.Now().Unix() - gen := hclwrite.NewEmptyFile() triggerBody := gen.Body().AppendNewBlock("trigger", nil).Body() triggerBody.SetAttributeValue("ctime", cty.NumberIntVal(ctime)) @@ -256,9 +254,9 @@ func Create(root *config.Root, path project.Path, kind Kind, reason string) erro triggerBody.SetAttributeRaw("type", hclwrite.TokensForIdentifier(string(kind))) triggerBody.SetAttributeRaw("context", hclwrite.TokensForIdentifier(DefaultContext)) - triggerPath := filepath.Join(triggerDir, filename) + triggerPath := triggerDir.Join(filename) - if err := os.WriteFile(triggerPath, gen.Bytes(), 0666); err != nil { + if err := stdos.WriteFile(triggerPath.String(), gen.Bytes(), 0666); err != nil { return errors.E(ErrTrigger, err, "creating trigger file") } diff --git a/stack/trigger/trigger_test.go b/stack/trigger/trigger_test.go index 4f15229839..48e3347cc4 100644 --- a/stack/trigger/trigger_test.go +++ b/stack/trigger/trigger_test.go @@ -6,7 +6,6 @@ package trigger_test import ( "fmt" "math" - "path/filepath" "strings" "testing" @@ -121,7 +120,7 @@ func testTrigger(t *testing.T, tc testcase) { } // check created trigger on fs - triggerDir := filepath.Join(trigger.Dir(root.HostDir()), tc.path) + triggerDir := trigger.Dir(root.Path()).Join(tc.path) entries := test.ReadDir(t, triggerDir) if len(entries) != 1 { t.Fatalf("want 1 trigger file, got %d: %+v", len(entries), entries) @@ -131,7 +130,7 @@ func testTrigger(t *testing.T, tc testcase) { if !strings.HasPrefix(filename, string(tc.kind)) { t.Fatalf("wrong trigger filename: %s (it must have a %q prefix)", filename, tc.kind) } - triggerFile := filepath.Join(triggerDir, entries[0].Name()) + triggerFile := triggerDir.Join(entries[0].Name()) triggerInfo, err := trigger.ParseFile(triggerFile) assert.NoError(t, err) @@ -142,7 +141,7 @@ func testTrigger(t *testing.T, tc testcase) { assert.EqualStrings(t, trigger.DefaultContext, triggerInfo.Context) assert.EqualStrings(t, string(tc.want.kind), string(triggerInfo.Type)) - gotPath, ok := trigger.StackPath(project.PrjAbsPath(root.HostDir(), triggerFile)) + gotPath, ok := trigger.StackPath(project.PrjAbsPath(root.Path(), triggerFile)) assert.IsTrue(t, ok) assert.EqualStrings(t, tc.path, gotPath.String()) } diff --git a/stdlib/funcs.go b/stdlib/funcs.go index d4ed2a0873..af50801e9b 100644 --- a/stdlib/funcs.go +++ b/stdlib/funcs.go @@ -5,7 +5,7 @@ package stdlib import ( "fmt" - "os" + stdos "os" "path/filepath" "regexp" "slices" @@ -20,6 +20,7 @@ import ( "github.com/terramate-io/terramate/event" "github.com/terramate-io/terramate/hcl/ast" "github.com/terramate-io/terramate/modvendor" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/tf" "github.com/terramate-io/terramate/versions" @@ -35,12 +36,9 @@ func init() { // Functions returns all the Terramate default functions. // The `basedir` must be an absolute path for an existent directory or it panics. -func Functions(basedir string, experiments []string) map[string]function.Function { - if !filepath.IsAbs(basedir) { - panic(errors.E(errors.ErrInternal, "context created with relative path: %q", basedir)) - } - - st, err := os.Stat(basedir) +func Functions(basedir os.Path, experiments []string) map[string]function.Function { + basestr := basedir.String() + st, err := stdos.Stat(basestr) if err != nil { panic(errors.E(errors.ErrInternal, err, "failed to stat context basedir %q", basedir)) } @@ -48,7 +46,7 @@ func Functions(basedir string, experiments []string) map[string]function.Functio panic(errors.E(errors.ErrInternal, "context basedir (%s) must be a directory", basedir)) } - scope := &lang.Scope{BaseDir: basedir} + scope := &lang.Scope{BaseDir: basestr} tffuncs := scope.Functions() // not supported functions @@ -89,7 +87,7 @@ func Functions(basedir string, experiments []string) map[string]function.Functio // NoFS returns all Terramate functions but excluding fs-related // functions. -func NoFS(basedir string, experiments []string) map[string]function.Function { +func NoFS(basedir os.Path, experiments []string) map[string]function.Function { funcs := Functions(basedir, experiments) fsFuncNames := []string{ "tm_abspath", @@ -260,7 +258,7 @@ func regexPatternResult(re *regexp.Regexp, str string, captureIdxs []int, retTyp func Name(name string) string { return "tm_" + name } // AbspathFunc returns the `tm_abspath()` hcl function. -func AbspathFunc(basedir string) function.Function { +func AbspathFunc(basedir os.Path) function.Function { return function.New(&function.Spec{ Params: []function.Parameter{ { @@ -271,14 +269,8 @@ func AbspathFunc(basedir string) function.Function { Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, _ cty.Type) (cty.Value, error) { path := args[0].AsString() - var abspath string - if filepath.IsAbs(path) { - abspath = path - } else { - abspath = filepath.Join(basedir, path) - } - - return cty.StringVal(filepath.Clean(abspath)), nil + abspath := os.HostPath(basedir, path) + return cty.StringVal(filepath.Clean(abspath.String())), nil }, }) } diff --git a/stdlib/funcs_test.go b/stdlib/funcs_test.go index 7fef7e9f7e..f75272c40a 100644 --- a/stdlib/funcs_test.go +++ b/stdlib/funcs_test.go @@ -4,7 +4,6 @@ package stdlib_test import ( - "path/filepath" "strings" "testing" @@ -316,7 +315,7 @@ func TestStdlibNewFunctionsMustPanicIfBasedirIsNonExistent(t *testing.T) { } }() - stdlib.Functions(filepath.Join(test.TempDir(t), "non-existent"), []string{}) + stdlib.Functions(test.TempDir(t).Join("non-existent"), []string{}) } func TestStdlibNewFunctionsFailIfBasedirIsNotADirectory(t *testing.T) { diff --git a/test/config.go b/test/config.go index dcd24a9009..1c318a4797 100644 --- a/test/config.go +++ b/test/config.go @@ -4,16 +4,15 @@ package test import ( - "path/filepath" - "github.com/terramate-io/terramate/config" + "github.com/terramate-io/terramate/os" ) // FixupRangeOnAsserts fixes the range on all the given asserts. // It assumes the asserts where created with relative paths and will // join the relative path with the given dir to provide a final absolute path. -func FixupRangeOnAsserts(dir string, asserts []config.Assert) { +func FixupRangeOnAsserts(dir os.Path, asserts []config.Assert) { for i := range asserts { - asserts[i].Range.Filename = filepath.Join(dir, asserts[i].Range.Filename) + asserts[i].Range.Filename = dir.Join(asserts[i].Range.Filename).String() } } diff --git a/test/fs.go b/test/fs.go index ac29c8bb95..699a87e67c 100644 --- a/test/fs.go +++ b/test/fs.go @@ -5,25 +5,25 @@ package test import ( "io/fs" - "os" - "path/filepath" + stdos "os" "testing" "github.com/google/go-cmp/cmp" "github.com/madlambda/spells/assert" + "github.com/terramate-io/terramate/os" ) // AssertTreeEquals asserts that the two given directories // are the same. This means they must have the same files and // also same subdirs with same files inside recursively. -func AssertTreeEquals(t *testing.T, dir1, dir2 string) { +func AssertTreeEquals(t *testing.T, dir1, dir2 os.Path) { t.Helper() entries1 := ReadDir(t, dir1) for _, entry1 := range entries1 { - path1 := filepath.Join(dir1, entry1.Name()) - path2 := filepath.Join(dir2, entry1.Name()) + path1 := dir1.Join(entry1.Name()) + path2 := dir2.Join(entry1.Name()) if entry1.IsDir() { AssertTreeEquals(t, path1, path2) @@ -36,13 +36,13 @@ func AssertTreeEquals(t *testing.T, dir1, dir2 string) { // AssertFileEquals asserts that the two given files are the same. // It assumes they are text files and shows a diff in case they are not the same. -func AssertFileEquals(t *testing.T, filepath1, filepath2 string) { +func AssertFileEquals(t *testing.T, filepath1, filepath2 os.Path) { t.Helper() - file1, err := os.ReadFile(filepath1) + file1, err := stdos.ReadFile(filepath1.String()) assert.NoError(t, err) - file2, err := os.ReadFile(filepath2) + file2, err := stdos.ReadFile(filepath2.String()) assert.NoError(t, err) if diff := cmp.Diff(string(file1), string(file2)); diff != "" { @@ -52,15 +52,15 @@ func AssertFileEquals(t *testing.T, filepath1, filepath2 string) { // AssertFileContentEquals asserts that file fname has the content of want. // It assumes the file content is a unicode string. -func AssertFileContentEquals(t *testing.T, fname string, want string) { +func AssertFileContentEquals(t *testing.T, fname os.Path, want string) { t.Helper() - got := ReadFile(t, filepath.Dir(fname), filepath.Base(fname)) + got := ReadFile(t, fname.Dir().String(), fname.Base()) if diff := cmp.Diff(string(got), string(want)); diff != "" { t.Fatalf("-(%s) +(%s):\n%s", got, want, diff) } } // AssertChmod is a portable version of the os.AssertChmod. -func AssertChmod(t testing.TB, fname string, mode fs.FileMode) { - assert.NoError(t, Chmod(fname, mode)) +func AssertChmod(t testing.TB, fname os.Path, mode fs.FileMode) { + assert.NoError(t, Chmod(fname.String(), mode)) } diff --git a/test/git.go b/test/git.go index b98896abfd..4b179aa002 100644 --- a/test/git.go +++ b/test/git.go @@ -8,6 +8,7 @@ import ( "github.com/madlambda/spells/assert" "github.com/terramate-io/terramate/git" + "github.com/terramate-io/terramate/os" ) const ( @@ -24,7 +25,7 @@ const ( // NewGitWrapper tests the creation of a git wrapper and returns it if success. // The env is the list of environment variables to be passed to git but if nil // is provided then the complete os.Environ() is used. -func NewGitWrapper(t testing.TB, wd string, env []string) *git.Git { +func NewGitWrapper(t testing.TB, wd os.Path, env []string) *git.Git { t.Helper() gw, err := git.WithConfig(git.Config{ @@ -42,7 +43,7 @@ func NewGitWrapper(t testing.TB, wd string, env []string) *git.Git { // EmptyRepo creates and initializes a git repository and checks for errors. // If bare is provided, the repository is for revisions (ie: for pushs) -func EmptyRepo(t testing.TB, bare bool) string { +func EmptyRepo(t testing.TB, bare bool) os.Path { t.Helper() gw := NewGitWrapper(t, "", []string{}) @@ -60,7 +61,7 @@ func EmptyRepo(t testing.TB, bare bool) string { // to the local "bare" repository and push a initial main commit onto // origin/main. The working git repository is returned and the other is // automatically cleaned up when the test function finishes. -func NewRepo(t testing.TB) string { +func NewRepo(t testing.TB) os.Path { t.Helper() repoDir := EmptyRepo(t, false) @@ -68,11 +69,11 @@ func NewRepo(t testing.TB) string { gw := NewGitWrapper(t, repoDir, []string{}) - err := gw.RemoteAdd("origin", remoteDir) + err := gw.RemoteAdd("origin", remoteDir.String()) assert.NoError(t, err, "git remote add origin") path := WriteFile(t, repoDir, "README.md", "# generated by terramate tests") - assert.NoError(t, gw.Add(path), "adding README.md to remote repo") + assert.NoError(t, gw.Add(path.String()), "adding README.md to remote repo") assert.NoError(t, gw.Commit("add readme")) err = gw.Push("origin", "main") diff --git a/test/hcl.go b/test/hcl.go index 74f829855f..51fef3f70c 100644 --- a/test/hcl.go +++ b/test/hcl.go @@ -17,13 +17,14 @@ import ( "github.com/terramate-io/terramate/hcl" "github.com/terramate-io/terramate/hcl/ast" "github.com/terramate-io/terramate/hcl/info" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "golang.org/x/exp/slices" ) // ParseTerramateConfig parses the Terramate configuration found // on the given dir, returning the parsed configuration. -func ParseTerramateConfig(t *testing.T, dir string) hcl.Config { +func ParseTerramateConfig(t *testing.T, dir os.Path) hcl.Config { t.Helper() parser, err := hcl.NewTerramateParser(dir, dir) @@ -170,7 +171,7 @@ func AssertEqualRanges(t *testing.T, got, want info.Range, fmtargs ...any) { msg := prefixer(fmtargs...) - assert.EqualStrings(t, want.HostPath(), got.HostPath(), msg("host path mismatch")) + assert.EqualStrings(t, want.HostPath().String(), got.HostPath().String(), msg("host path mismatch")) AssertEqualPaths(t, got.Path(), want.Path(), msg("path mismatch")) AssertEqualPos(t, got.Start(), want.Start(), msg("start pos mismatch")) AssertEqualPos(t, got.End(), want.End(), msg("end pos mismatch")) @@ -444,7 +445,7 @@ func hclFromAttributes(t *testing.T, attrs ast.Attributes) string { } // WriteRootConfig writes a basic terramate root config. -func WriteRootConfig(t testing.TB, rootdir string) { +func WriteRootConfig(t testing.TB, rootdir os.Path) { WriteFile(t, rootdir, "root.config.tm", ` terramate { required_version = "> 0.0.1" diff --git a/test/hclutils/info/info.go b/test/hclutils/info/info.go index 5db12d3ee9..a2420a28d4 100644 --- a/test/hclutils/info/info.go +++ b/test/hclutils/info/info.go @@ -5,12 +5,13 @@ package info import ( - "os" + stdos "os" "path/filepath" hhcl "github.com/terramate-io/hcl/v2" "github.com/terramate-io/terramate/hcl" "github.com/terramate-io/terramate/hcl/info" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/test/hclutils" ) @@ -21,7 +22,7 @@ import ( // compared to actual results. func Range(fname string, start, end hhcl.Pos) info.Range { if !filepath.IsAbs(fname) { - fname = string(os.PathSeparator) + fname + fname = string(stdos.PathSeparator) + fname } return info.NewRange("/", hhcl.Range{ Filename: fname, @@ -33,7 +34,7 @@ func Range(fname string, start, end hhcl.Pos) info.Range { // FixRangesOnConfig fix the ranges on the given HCL config. // This is necessary since on tests we don't know the sandbox project // path, so host absolute paths must be updated here. -func FixRangesOnConfig(dir string, cfg hcl.Config) { +func FixRangesOnConfig(dir os.Path, cfg hcl.Config) { for i := range cfg.Asserts { cfg.Asserts[i].Range = FixRange(dir, cfg.Asserts[i].Range) } @@ -54,7 +55,7 @@ func FixRangesOnConfig(dir string, cfg hcl.Config) { // FixRange fix the given range. // This is necessary since on tests we don't know the sandbox project // path, so host absolute paths must be updated here. -func FixRange(rootdir string, old info.Range) info.Range { +func FixRange(rootdir os.Path, old info.Range) info.Range { // When defining test cases there is no way to know the final // absolute paths since sandboxes are dynamic/temporary. // So we use relative paths as host paths and make them absolute here. @@ -64,7 +65,8 @@ func FixRange(rootdir string, old info.Range) info.Range { // avoid panics since the paths are not valid (empty strings). return old } - filename := filepath.Join(rootdir, old.HostPath()) + // TODO(i4k): review this!!!! + filename := rootdir.Join(old.HostPath().String()) return info.NewRange(rootdir, hclutils.Mkrange(filename, hclutils.Start( old.Start().Line(), @@ -76,7 +78,7 @@ func FixRange(rootdir string, old info.Range) info.Range { old.End().Byte()))) } -func fixRangeOnAsserts(dir string, asserts []hcl.AssertConfig) { +func fixRangeOnAsserts(dir os.Path, asserts []hcl.AssertConfig) { for i := range asserts { asserts[i].Range = FixRange(dir, asserts[i].Range) } diff --git a/test/hclutils/utils.go b/test/hclutils/utils.go index 0d5b2b8aa2..ddeaf2b4b5 100644 --- a/test/hclutils/utils.go +++ b/test/hclutils/utils.go @@ -5,25 +5,24 @@ package hclutils import ( - "path/filepath" - hhcl "github.com/terramate-io/hcl/v2" "github.com/terramate-io/terramate/errors" + "github.com/terramate-io/terramate/os" ) // FixupFiledirOnErrorsFileRanges fix the filename in the ranges of the error list. -func FixupFiledirOnErrorsFileRanges(dir string, errs []error) { +func FixupFiledirOnErrorsFileRanges(dir os.Path, errs []error) { for _, err := range errs { if e, ok := err.(*errors.Error); ok { - e.FileRange.Filename = filepath.Join(dir, e.FileRange.Filename) + e.FileRange.Filename = dir.Join(e.FileRange.Filename).String() } } } // Mkrange builds a file range. -func Mkrange(fname string, start, end hhcl.Pos) hhcl.Range { +func Mkrange(fname os.Path, start, end hhcl.Pos) hhcl.Range { return hhcl.Range{ - Filename: fname, + Filename: fname.String(), Start: start, End: end, } diff --git a/test/ls/editor.go b/test/ls/editor.go index 9b72142fc9..91938c1988 100644 --- a/test/ls/editor.go +++ b/test/ls/editor.go @@ -6,14 +6,14 @@ package ls import ( "context" "encoding/json" - "os" - "path/filepath" + stdos "os" "testing" "github.com/google/go-cmp/cmp" "github.com/madlambda/spells/assert" "github.com/rs/zerolog" tmls "github.com/terramate-io/terramate/ls" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/test/sandbox" "go.lsp.dev/jsonrpc2" lsp "go.lsp.dev/protocol" @@ -54,13 +54,13 @@ func (e *Editor) call(method string, params, result interface{}) (jsonrpc2.ID, e // Initialize sends a initialize request to the language server and return its // result. -func (e *Editor) Initialize(workspace string) lsp.InitializeResult { +func (e *Editor) Initialize(workspace os.Path) lsp.InitializeResult { e.t.Helper() var got lsp.InitializeResult _, err := e.call( lsp.MethodInitialize, lsp.InitializeParams{ - RootURI: uri.File(workspace), + RootURI: uri.File(workspace.String()), }, &got) @@ -70,7 +70,7 @@ func (e *Editor) Initialize(workspace string) lsp.InitializeResult { // CheckInitialize sends an initialize request to the language server and checks // if the response is the expected default response (See DefaultInitializeResult). -func (e *Editor) CheckInitialize(workspace string) { +func (e *Editor) CheckInitialize(workspace os.Path) { e.t.Helper() got := e.Initialize(workspace) if diff := cmp.Diff(got, DefaultInitializeResult()); diff != "" { @@ -90,13 +90,13 @@ func (e *Editor) CheckInitialize(workspace string) { func (e *Editor) Open(path string) { t := e.t t.Helper() - abspath := filepath.Join(e.sandbox.RootDir(), path) - fileContents, err := os.ReadFile(abspath) + abspath := e.sandbox.RootDir().Join(path) + fileContents, err := stdos.ReadFile(abspath.String()) assert.NoError(t, err, "reading stack file %q", path) var openResult interface{} _, err = e.call(lsp.MethodTextDocumentDidOpen, lsp.DidOpenTextDocumentParams{ TextDocument: lsp.TextDocumentItem{ - URI: uri.File(abspath), + URI: uri.File(abspath.String()), LanguageID: "terramate", Text: string(fileContents), }, @@ -111,12 +111,12 @@ func (e *Editor) Open(path string) { func (e *Editor) Change(path, content string) { t := e.t t.Helper() - abspath := filepath.Join(e.sandbox.RootDir(), path) + abspath := e.sandbox.RootDir().Join(path) var changeResult interface{} _, err := e.call(lsp.MethodTextDocumentDidChange, lsp.DidChangeTextDocumentParams{ TextDocument: lsp.VersionedTextDocumentIdentifier{ TextDocumentIdentifier: lsp.TextDocumentIdentifier{ - URI: uri.File(abspath), + URI: uri.File(abspath.String()), }, }, ContentChanges: []lsp.TextDocumentContentChangeEvent{ diff --git a/test/os.go b/test/os.go index de42094101..22323c5557 100644 --- a/test/os.go +++ b/test/os.go @@ -6,36 +6,38 @@ package test import ( "errors" "io/fs" - "os" + + stdos "os" "path/filepath" "runtime" "strings" "testing" "github.com/madlambda/spells/assert" + "github.com/terramate-io/terramate/os" ) -var tmTestRootTempdir string +var tmTestRootTempdir os.Path func init() { - tmTestRootTempdir = os.Getenv("TM_TEST_ROOT_TEMPDIR") + tmTestRootTempdir = os.NewHostPath(stdos.Getenv("TM_TEST_ROOT_TEMPDIR")) } // TempDir creates a temporary directory. -func TempDir(t testing.TB) string { +func TempDir(t testing.TB) os.Path { t.Helper() if tmTestRootTempdir == "" { // fallback for the slower implementation if env is not set. - return t.TempDir() + return os.NewHostPath(t.TempDir()) } return tempDir(t, tmTestRootTempdir) } // DoesNotExist calls os.Stat and asserts that the entry does not exist -func DoesNotExist(t testing.TB, dir, fname string) { +func DoesNotExist(t testing.TB, dir os.Path, fname string) { t.Helper() - _, err := os.Stat(filepath.Join(dir, fname)) - if errors.Is(err, os.ErrNotExist) { + _, err := stdos.Stat(dir.Join(fname).String()) + if errors.Is(err, stdos.ErrNotExist) { return } assert.NoError(t, err, "stat error") @@ -57,8 +59,8 @@ func IsFile(t testing.TB, dir, fname string) { func isDirOrFile(t testing.TB, dir, fname string, isDir bool) { t.Helper() - fi, err := os.Stat(filepath.Join(dir, fname)) - if errors.Is(err, os.ErrNotExist) { + fi, err := stdos.Stat(filepath.Join(dir, fname)) + if errors.Is(err, stdos.ErrNotExist) { if isDir { t.Fatalf("directory does not exist: %s", fname) } else { @@ -72,27 +74,27 @@ func isDirOrFile(t testing.TB, dir, fname string, isDir bool) { } // ReadDir calls os.Readir asserting the success of the operation. -func ReadDir(t testing.TB, dir string) []os.DirEntry { +func ReadDir(t testing.TB, dir os.Path) []stdos.DirEntry { t.Helper() - entries, err := os.ReadDir(dir) + entries, err := stdos.ReadDir(dir.String()) assert.NoError(t, err) return entries } // WriteFile writes content to a filename inside dir directory. // If dir is empty string then the file is created inside a temporary directory. -func WriteFile(t testing.TB, dir string, filename string, content string) string { +func WriteFile(t testing.TB, dir os.Path, filename string, content string) os.Path { t.Helper() if dir == "" { dir = TempDir(t) } - path := filepath.Join(dir, filename) - pathdir := filepath.Dir(path) - MkdirAll(t, pathdir) - err := os.WriteFile(path, []byte(content), 0700) + path := dir.Join(filename) + pathdir := path.Dir() + MkdirAll(t, pathdir.String()) + err := stdos.WriteFile(path.String(), []byte(content), 0700) assert.NoError(t, err, "writing test file %s", path) return path @@ -101,11 +103,11 @@ func WriteFile(t testing.TB, dir string, filename string, content string) string // AppendFile appends content to a filename inside dir directory. // If file exists, appends on the end of it by adding a newline, // if file doesn't exists it will be created. -func AppendFile(t testing.TB, dir string, filename string, content string) { +func AppendFile(t testing.TB, dir os.Path, filename string, content string) { t.Helper() - oldContent, err := os.ReadFile(filepath.Join(dir, filename)) - if err != nil && !os.IsNotExist(err) { + oldContent, err := stdos.ReadFile(dir.Join(filename).String()) + if err != nil && !stdos.IsNotExist(err) { t.Fatal(err) } @@ -116,23 +118,23 @@ func AppendFile(t testing.TB, dir string, filename string, content string) { // ReadFile reads the content of fname from dir directory. func ReadFile(t testing.TB, dir, fname string) []byte { t.Helper() - data, err := os.ReadFile(filepath.Join(dir, fname)) + data, err := stdos.ReadFile(filepath.Join(dir, fname)) assert.NoError(t, err, "reading file") return data } // RemoveFile removes the file fname from dir directory. // If the files doesn't exists, it succeeds. -func RemoveFile(t testing.TB, dir, fname string) { +func RemoveFile(t testing.TB, dir os.Path, fname string) { t.Helper() - err := os.Remove(filepath.Join(dir, fname)) + err := stdos.Remove(dir.Join(fname).String()) assert.NoError(t, err) } // Mkdir creates a directory inside base. -func Mkdir(t testing.TB, base string, name string) string { - path := filepath.Join(base, name) - assert.NoError(t, os.Mkdir(path, 0700), "creating dir") +func Mkdir(t testing.TB, base os.Path, name string) os.Path { + path := base.Join(name) + assert.NoError(t, stdos.Mkdir(path.String(), 0700), "creating dir") return path } @@ -140,28 +142,28 @@ func Mkdir(t testing.TB, base string, name string) string { func MkdirAll(t testing.TB, path string) { t.Helper() - assert.NoError(t, os.MkdirAll(path, 0700), "failed to create temp directory") + assert.NoError(t, stdos.MkdirAll(path, 0700), "failed to create temp directory") } // MkdirAll2 creates a temporary directory with provided permissions. func MkdirAll2(t testing.TB, path string, perm fs.FileMode) { t.Helper() - assert.NoError(t, os.MkdirAll(path, perm), "failed to create temp directory") + assert.NoError(t, stdos.MkdirAll(path, perm), "failed to create temp directory") } // Symlink calls [os.Symlink] failing the test if there is an error. func Symlink(t testing.TB, oldname, newname string) { t.Helper() - assert.NoError(t, os.Symlink(oldname, newname), "failed to create symlink") + assert.NoError(t, stdos.Symlink(oldname, newname), "failed to create symlink") } // Getwd gets the current working dir of the process func Getwd(t testing.TB) string { t.Helper() - wd, err := os.Getwd() + wd, err := stdos.Getwd() assert.NoError(t, err) return wd } @@ -180,31 +182,30 @@ func RelPath(t testing.TB, basepath, targetpath string) string { func RemoveAll(t testing.TB, path string) { t.Helper() - assert.NoError(t, os.RemoveAll(path), "failed to remove directory %q", path) + assert.NoError(t, stdos.RemoveAll(path), "failed to remove directory %q", path) } // NonExistingDir returns a non-existing directory. -func NonExistingDir(t testing.TB) string { +func NonExistingDir(t testing.TB) os.Path { t.Helper() tmp := TempDir(t) tmp2 := tempDir(t, tmp) - RemoveAll(t, tmp) - + RemoveAll(t, tmp.String()) return tmp2 } // CanonPath returns a canonical absolute path for the given path. // Fails the test if any error is found. -func CanonPath(t testing.TB, path string) string { +func CanonPath(t testing.TB, path os.Path) os.Path { t.Helper() - p, err := filepath.EvalSymlinks(path) + p, err := filepath.EvalSymlinks(path.String()) assert.NoError(t, err) p, err = filepath.Abs(p) assert.NoError(t, err) - return p + return os.NewHostPath(p) } // PrependToPath prepend a directory to the OS PATH variable in a portable way. @@ -221,7 +222,7 @@ func PrependToPath(env []string, dir string) ([]string, bool) { key := v[:eqPos] oldv := v[eqPos+1:] if envKeyEquality(key, "PATH") { - v = key + "=" + dir + string(os.PathListSeparator) + oldv + v = key + "=" + dir + string(stdos.PathListSeparator) + oldv env[i] = v return env, true } @@ -229,8 +230,8 @@ func PrependToPath(env []string, dir string) ([]string, bool) { return env, false } -func tempDir(t testing.TB, base string) string { - dir, err := os.MkdirTemp(base, "terramate-test") +func tempDir(t testing.TB, base os.Path) os.Path { + dir, err := stdos.MkdirTemp(base.String(), "terramate-test") assert.NoError(t, err, "creating temp directory") - return dir + return os.NewHostPath(dir) } diff --git a/test/sandbox/git.go b/test/sandbox/git.go index 8ae2c55bbb..a48d8dcaac 100644 --- a/test/sandbox/git.go +++ b/test/sandbox/git.go @@ -10,6 +10,7 @@ import ( "github.com/madlambda/spells/assert" "github.com/terramate-io/terramate/git" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/test" ) @@ -19,7 +20,7 @@ type GitConfig struct { DefaultRemoteName string DefaultRemoteBranchName string - repoDir string + repoDir os.Path } // Git is a git wrapper that makes testing easy by handling @@ -28,11 +29,11 @@ type Git struct { t testing.TB g *git.Git cfg GitConfig - bareRepo string + bareRepo os.Path } // NewGit creates a new git wrapper using sandbox defaults. -func NewGit(t testing.TB, repodir string) *Git { +func NewGit(t testing.TB, repodir os.Path) *Git { t.Helper() cfg := defaultGitConfig() @@ -68,14 +69,14 @@ func (git *Git) Init() { // So we can test if disjoint branches are not reachable (ie. no merge-base). path := test.WriteFile(t, git.cfg.repoDir, "README.md", fmt.Sprintf("# generated by terramate (entropy %d)", time.Now().UnixNano())) - git.Add(path) + git.Add(path.String()) git.Commit("first commit") git.configureDefaultRemote() } // BareRepoAbsPath returns the path for the bare remote repository of this // repository. -func (git *Git) BareRepoAbsPath() string { +func (git *Git) BareRepoAbsPath() os.Path { git.t.Helper() if git.bareRepo == "" { git.t.Fatal("baregit not initialized") @@ -86,7 +87,7 @@ func (git *Git) BareRepoAbsPath() string { func (git *Git) configureDefaultRemote() { cfg := git.cfg remoteRepo := git.initRemoteRepo(cfg.DefaultRemoteBranchName) - git.RemoteAdd(cfg.DefaultRemoteName, remoteRepo) + git.RemoteAdd(cfg.DefaultRemoteName, remoteRepo.String()) // Pushes current branch onto defRemote and defBranch git.PushOn(cfg.DefaultRemoteName, cfg.DefaultRemoteBranchName, cfg.LocalBranchName) @@ -96,11 +97,11 @@ func (git *Git) configureDefaultRemote() { // using remoteName and remoteBranch. func (git Git) SetupRemote(remoteName, remoteBranch, localBranch string) { remoteRepo := git.initRemoteRepo(remoteBranch) - git.RemoteAdd(remoteName, remoteRepo) + git.RemoteAdd(remoteName, remoteRepo.String()) git.PushOn(remoteName, remoteBranch, localBranch) } -func (git *Git) initRemoteRepo(branchName string) string { +func (git *Git) initRemoteRepo(branchName string) os.Path { t := git.t t.Helper() @@ -189,7 +190,7 @@ func (git Git) Commit(msg string, args ...string) { } // Clone will clone a repository into the given dir. -func (git Git) Clone(repoURL, dir string) { +func (git Git) Clone(repoURL string, dir os.Path) { git.t.Helper() if err := git.g.Clone(repoURL, dir); err != nil { @@ -274,7 +275,7 @@ func (git Git) SetRemoteURL(remote, url string) { } // BaseDir the repository base dir -func (git Git) BaseDir() string { +func (git Git) BaseDir() os.Path { return git.cfg.repoDir } diff --git a/test/sandbox/git_test.go b/test/sandbox/git_test.go index e23ade38bf..cd9a75acb7 100644 --- a/test/sandbox/git_test.go +++ b/test/sandbox/git_test.go @@ -27,7 +27,7 @@ func TestInitializeArbitraryRemote(t *testing.T) { // the main branch only exists after first commit. path := test.WriteFile(t, git.BaseDir(), "README.md", "# generated by terramate") - git.Add(path) + git.Add(path.String()) git.Commit("first commit") const remote = "mineiros" diff --git a/test/sandbox/sandbox.go b/test/sandbox/sandbox.go index 1327a8a18a..f9967f7b4f 100644 --- a/test/sandbox/sandbox.go +++ b/test/sandbox/sandbox.go @@ -15,7 +15,7 @@ import ( "bytes" "fmt" stdfs "io/fs" - "os" + stdos "os" "os/exec" "path" "path/filepath" @@ -32,6 +32,7 @@ import ( "github.com/terramate-io/terramate/globals" "github.com/terramate-io/terramate/hcl" "github.com/terramate-io/terramate/hcl/eval" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/terramate-io/terramate/run" "github.com/terramate-io/terramate/stack" @@ -43,7 +44,7 @@ import ( type S struct { t testing.TB git *Git - rootdir string + rootdir os.Path cfg *config.Root Env []string @@ -53,8 +54,8 @@ type S struct { // directory. type DirEntry struct { t testing.TB - rootpath string - abspath string + rootpath os.Path + abspath os.Path relpath string } @@ -70,8 +71,8 @@ type StackEntry struct { // It has limited usefulness but it is easier to work with for testing. type FileEntry struct { t testing.TB - hostpath string - rootpath string + hostpath os.Path + rootpath os.Path } // New creates a new complete test sandbox. @@ -120,8 +121,8 @@ func NoGit(t testing.TB, createProject bool) S { "s:other-hidden-stack", }) - rootdir := filepath.Join(outerDir, "sandbox") - test.MkdirAll(t, rootdir) + rootdir := outerDir.Join("sandbox") + test.MkdirAll(t, rootdir.String()) if createProject { test.WriteRootConfig(t, rootdir) @@ -307,7 +308,7 @@ func (s S) LoadStackGlobals( // // It is a programming error to delete this dir, it will be automatically // removed when the test finishes. -func (s S) RootDir() string { +func (s S) RootDir() os.Path { return s.rootdir } @@ -393,8 +394,8 @@ func (s S) DirEntry(relpath string) DirEntry { t.Fatalf("DirEntry() needs a relative path but given %q", relpath) } - abspath := filepath.Join(s.rootdir, filepath.FromSlash(relpath)) - stat, err := os.Stat(abspath) + abspath := s.rootdir.Join(relpath) + stat, err := stdos.Stat(abspath.String()) if err != nil { t.Fatalf("DirEntry(): dir must exist: %v", err) } @@ -426,7 +427,7 @@ func (de DirEntry) CreateFile(name, body string, args ...interface{}) FileEntry fe := FileEntry{ t: de.t, rootpath: de.rootpath, - hostpath: filepath.Join(de.abspath, name), + hostpath: de.abspath.Join(name), } fe.Write(body, args...) @@ -456,7 +457,7 @@ func (de DirEntry) CreateConfig(body string) FileEntry { fe := FileEntry{ t: de.t, - hostpath: filepath.Join(de.abspath, config.DefaultFilename), + hostpath: de.abspath.Join(config.DefaultFilename), } fe.Write(body) return fe @@ -467,7 +468,7 @@ func (de DirEntry) DeleteConfig() { de.t.Helper() assert.NoError(de.t, - os.Remove(filepath.Join(de.abspath, config.DefaultFilename)), + stdos.Remove(filepath.Join(de.abspath.String(), config.DefaultFilename)), "removing default configuration file") } @@ -480,7 +481,7 @@ func (de DirEntry) CreateDir(relpath string) DirEntry { // Chmod does the same as [test.Chmod] for the given file/dir inside // this DirEntry. func (de DirEntry) Chmod(relpath string, mode stdfs.FileMode) { - test.AssertChmod(de.t, filepath.Join(de.abspath, relpath), mode) + test.AssertChmod(de.t, de.abspath.Join(relpath), mode) } // ReadFile will read a file inside this dir entry with the given name. @@ -488,7 +489,7 @@ func (de DirEntry) Chmod(relpath string, mode stdfs.FileMode) { // expectation on the file being there. func (de DirEntry) ReadFile(name string) []byte { de.t.Helper() - return test.ReadFile(de.t, de.abspath, name) + return test.ReadFile(de.t, de.abspath.String(), name) } // RemoveFile will delete a file inside this dir entry with the given name. @@ -499,7 +500,7 @@ func (de DirEntry) RemoveFile(name string) { } // Path returns the absolute path of the directory entry. -func (de DirEntry) Path() string { +func (de DirEntry) Path() os.Path { return de.abspath } @@ -525,22 +526,22 @@ func (fe FileEntry) Write(body string, args ...interface{}) { body = fmt.Sprintf(body, args...) - test.MkdirAll(fe.t, filepath.Dir(fe.hostpath)) + test.MkdirAll(fe.t, fe.hostpath.Dir().String()) - if err := os.WriteFile(fe.hostpath, []byte(body), 0700); err != nil { + if err := stdos.WriteFile(fe.hostpath.String(), []byte(body), 0700); err != nil { fe.t.Fatalf("os.WriteFile(%q) = %v", fe.hostpath, err) } } // Chmod changes the file mod, like os.Chmod. -func (fe FileEntry) Chmod(mode os.FileMode) { +func (fe FileEntry) Chmod(mode stdos.FileMode) { fe.t.Helper() test.AssertChmod(fe.t, fe.hostpath, mode) } // HostPath returns the absolute path of the file. -func (fe FileEntry) HostPath() string { +func (fe FileEntry) HostPath() os.Path { return fe.hostpath } @@ -553,7 +554,7 @@ func (fe FileEntry) Path() string { // module dir entry. The path is relative to stack dir itself (hence suitable to // be a module source path). func (se StackEntry) ModSource(mod DirEntry) string { - relpath, err := filepath.Rel(se.abspath, mod.abspath) + relpath, err := filepath.Rel(se.abspath.String(), mod.abspath.String()) assert.NoError(se.t, err) return filepath.ToSlash(relpath) } @@ -582,13 +583,13 @@ func (se StackEntry) ReadFile(filename string) string { // Load loads the terramate stack instance for this stack dir entry. func (se StackEntry) Load(root *config.Root) *config.Stack { se.t.Helper() - loadedStack, err := config.LoadStack(root, project.PrjAbsPath(root.HostDir(), se.Path())) + loadedStack, err := config.LoadStack(root, project.PrjAbsPath(root.Path(), se.Path())) assert.NoError(se.t, err) return loadedStack } // Path returns the absolute path of the stack. -func (se StackEntry) Path() string { +func (se StackEntry) Path() os.Path { return se.DirEntry.abspath } @@ -598,11 +599,11 @@ func (se StackEntry) RelPath() string { return se.DirEntry.relpath } -func newDirEntry(t testing.TB, rootdir string, relpath string) DirEntry { +func newDirEntry(t testing.TB, rootdir os.Path, relpath string) DirEntry { t.Helper() - abspath := filepath.Join(rootdir, relpath) - test.MkdirAll(t, abspath) + abspath := rootdir.Join(relpath) + test.MkdirAll(t, abspath.String()) return DirEntry{ t: t, @@ -612,7 +613,7 @@ func newDirEntry(t testing.TB, rootdir string, relpath string) DirEntry { } } -func newStackEntry(t testing.TB, rootdir string, relpath string) StackEntry { +func newStackEntry(t testing.TB, rootdir os.Path, relpath string) StackEntry { return StackEntry{DirEntry: newDirEntry(t, rootdir, relpath)} } @@ -646,7 +647,7 @@ func parseListSpec(t testing.TB, name, value string) []string { func buildTree(t testing.TB, root *config.Root, environ []string, layout []string) { t.Helper() - rootdir := root.HostDir() + rootdir := root.Path() parseParams := func(spec string) (string, string) { colonIndex := strings.Index(spec, ":") + 1 tmp := spec[colonIndex:] @@ -666,8 +667,8 @@ func buildTree(t testing.TB, root *config.Root, environ []string, layout []strin genStackFile := func(relpath, data string) { attrs := strings.Split(data, ";") - cfgdir := filepath.Join(rootdir, filepath.FromSlash(relpath)) - test.MkdirAll(t, cfgdir) + cfgdir := rootdir.Join(relpath) + test.MkdirAll(t, cfgdir.String()) cfg, err := hcl.NewConfig(cfgdir) assert.NoError(t, err) @@ -709,19 +710,19 @@ func buildTree(t testing.TB, root *config.Root, environ []string, layout []strin specKind := string(spec[0:colonIndex]) switch specKind { case "d:", "dir:": - test.MkdirAll(t, filepath.Join(rootdir, spec[colonIndex:])) + test.MkdirAll(t, rootdir.Join(spec[colonIndex:]).String()) case "l:", "link:": - target := filepath.Join(rootdir, param1) - linkName := filepath.Join(rootdir, param2) - test.Symlink(t, target, linkName) + target := rootdir.Join(param1) + linkName := rootdir.Join(param2) + test.Symlink(t, target.String(), linkName.String()) case "g:", "git:": - repodir := filepath.Join(rootdir, spec[colonIndex:]) - test.MkdirAll(t, repodir) + repodir := rootdir.Join(spec[colonIndex:]) + test.MkdirAll(t, repodir.String()) git := NewGit(t, repodir) git.Init() case "s:", "stack:": if param2 == "" { - abspath := filepath.Join(rootdir, param1) + abspath := rootdir.Join(param1) stackdir := project.PrjAbsPath(rootdir, abspath) assert.NoError(t, stack.Create(root, config.Stack{Dir: stackdir})) continue @@ -731,16 +732,15 @@ func buildTree(t testing.TB, root *config.Root, environ []string, layout []strin case "f:", "file:": test.WriteFile(t, rootdir, param1, param2) case "copy:": - assert.NoError(t, fs.CopyAll( - filepath.Join(rootdir, param1), - param2, - )) + wd, err := stdos.Getwd() + assert.NoError(t, err) + assert.NoError(t, fs.CopyAll(rootdir.Join(param1), os.NewHostPath(wd).Join(param2))) case "run:": cmdParts := strings.Split(param2, " ") path, err := run.LookPath(cmdParts[0], environ) assert.NoError(t, err) cmd := exec.Command(path, cmdParts[1:]...) - cmd.Dir = filepath.Join(rootdir, param1) + cmd.Dir = rootdir.Join(param1).String() cmd.Env = environ out, err := cmd.CombinedOutput() assert.NoError(t, err, "failed to execute sandbox run: (output: %s)", out) @@ -761,7 +761,7 @@ func assertTree(t testing.TB, root *config.Root, layout []string, opts *assertTr return spec[0:idx], spec[idx+1:] } - rootdir := root.HostDir() + rootdir := root.Path() wantStrictStacks := []string{} @@ -771,17 +771,17 @@ func assertTree(t testing.TB, root *config.Root, layout []string, opts *assertTr switch specKind { case "d", "dir": dirname, _ := popArg(spec) - test.IsDir(t, rootdir, dirname) + test.IsDir(t, rootdir.String(), dirname) case "f", "file": fname, spec := popArg(spec) want, _ := popArg(spec) if want != "" { - got := string(test.ReadFile(t, rootdir, fname)) + got := string(test.ReadFile(t, rootdir.String(), fname)) assert.EqualStrings(t, want, got, "want:\n%s\ngot:\n%s\n", want, got) } else { - test.IsFile(t, rootdir, fname) + test.IsFile(t, rootdir.String(), fname) } case "!e", "not_exist": @@ -790,8 +790,10 @@ func assertTree(t testing.TB, root *config.Root, layout []string, opts *assertTr case "s", "stack": stackdir, _ := popArg(spec) - prjStackdir := project.PrjAbsPath(rootdir, stackdir) - + if !path.IsAbs(stackdir) { + stackdir = "/" + stackdir + } + prjStackdir := project.NewPath(stackdir) if opts.withStrictStacks { wantStrictStacks = append(wantStrictStacks, prjStackdir.String()) } else { diff --git a/test/stack.go b/test/stack.go index d60b26cf2c..f86d74e28e 100644 --- a/test/stack.go +++ b/test/stack.go @@ -10,13 +10,14 @@ import ( "github.com/madlambda/spells/assert" "github.com/terramate-io/terramate/config" "github.com/terramate-io/terramate/hcl" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" ) // AssertStackImports checks that the given stack has all the wanted import // definitions. The wanted imports is a slice of the sources that are imported // on each block. -func AssertStackImports(t *testing.T, rootdir string, stackHostPath string, want []string) { +func AssertStackImports(t *testing.T, rootdir os.Path, stackHostPath os.Path, want []string) { t.Helper() parser, err := hcl.NewTerramateParser(rootdir, stackHostPath) diff --git a/tf/mod_source_parse.go b/tf/mod_source_parse.go index 16b7f1e321..3c3dfea6b3 100644 --- a/tf/mod_source_parse.go +++ b/tf/mod_source_parse.go @@ -126,9 +126,6 @@ func ParseSource(modsource string) (Source, error) { pathstr := path.Join(strings.Replace(u.Host, ":", "-", -1), u.Path) pathstr = strings.TrimSuffix(pathstr, ".git") - if err != nil { - return Source{}, err - } ref := u.Query().Get("ref") u.RawQuery = "" return Source{ diff --git a/tf/terraform.go b/tf/terraform.go index c90e4ed781..6bff88e899 100644 --- a/tf/terraform.go +++ b/tf/terraform.go @@ -4,13 +4,14 @@ package tf import ( - "os" + stdos "os" "github.com/rs/zerolog/log" "github.com/terramate-io/hcl/v2/hclparse" "github.com/terramate-io/hcl/v2/hclsyntax" "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/hcl/ast" + "github.com/terramate-io/terramate/os" "github.com/zclconf/go-cty/cty" ) @@ -31,13 +32,13 @@ func (m Module) IsLocal() bool { } // ParseModules parses blocks of type "module" containing a single label. -func ParseModules(path string) ([]Module, error) { +func ParseModules(path os.Path) ([]Module, error) { logger := log.With(). Str("action", "ParseModules()"). - Str("path", path). + Stringer("path", path). Logger() - _, err := os.Stat(path) + _, err := stdos.Stat(path.String()) if err != nil { return nil, errors.E(err, "stat failed on %q", path) } @@ -46,7 +47,7 @@ func ParseModules(path string) ([]Module, error) { logger.Debug().Msg("Parse HCL file") - f, diags := p.ParseHCLFile(path) + f, diags := p.ParseHCLFile(path.String()) if diags.HasErrors() { return nil, errors.E(ErrHCLSyntax, diags) } @@ -93,7 +94,7 @@ func ParseModules(path string) ([]Module, error) { // IsStack tells if the file defined by path is a potential stack. // Eg.: has a backend block or a provider block. -func IsStack(path string) (bool, error) { +func IsStack(path os.Path) (bool, error) { logger := log.With(). Str("action", "IsStack"). Logger() @@ -102,7 +103,7 @@ func IsStack(path string) (bool, error) { logger.Debug().Msg("Parsing TF file") - f, diags := p.ParseHCLFile(path) + f, diags := p.ParseHCLFile(path.String()) if diags.HasErrors() { return false, errors.E(ErrHCLSyntax, diags) } diff --git a/tf/terraform_test.go b/tf/terraform_test.go index 994605b5bb..e2be71a0db 100644 --- a/tf/terraform_test.go +++ b/tf/terraform_test.go @@ -5,13 +5,13 @@ package tf_test import ( "fmt" - "path/filepath" "testing" "github.com/madlambda/spells/assert" "github.com/rs/zerolog" hhcl "github.com/terramate-io/hcl/v2" "github.com/terramate-io/terramate/errors" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/test" errtest "github.com/terramate-io/terramate/test/errors" "github.com/terramate-io/terramate/test/sandbox" @@ -356,7 +356,7 @@ func TestTerraformHasBackend(t *testing.T) { s := sandbox.NoGit(t, false) s.BuildTree(tc.layout) - path := filepath.Join(s.RootDir(), filename) + path := s.RootDir().Join(filename) hasBackend, err := tf.IsStack(path) errtest.Assert(t, err, tc.want.err) @@ -392,10 +392,10 @@ func start(line, column, char int) hhcl.Pos { } } -func fixupFiledirOnErrorsFileRanges(dir string, errs []error) { +func fixupFiledirOnErrorsFileRanges(dir os.Path, errs []error) { for _, err := range errs { if e, ok := err.(*errors.Error); ok { - e.FileRange.Filename = filepath.Join(dir, e.FileRange.Filename) + e.FileRange.Filename = dir.Join(e.FileRange.Filename).String() } } } diff --git a/tg/funcs.go b/tg/funcs.go index b19199fde8..5e9ca37141 100644 --- a/tg/funcs.go +++ b/tg/funcs.go @@ -23,22 +23,23 @@ package tg import ( "encoding/json" "fmt" - "os" + stdos "os" "path/filepath" "strings" "github.com/gruntwork-io/go-commons/errors" tgconfig "github.com/gruntwork-io/terragrunt/config" "github.com/gruntwork-io/terragrunt/util" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" ) -type tgFunction func(ctx *tgconfig.ParsingContext, rootdir string, mod *Module, args []string) (string, error) +type tgFunction func(ctx *tgconfig.ParsingContext, rootdir os.Path, mod *Module, args []string) (string, error) // findInParentFoldersFunc implements the Terragrunt `find_in_parent_folders` function. -func findInParentFoldersFunc(pctx *tgconfig.ParsingContext, rootdir string, mod *Module) function.Function { +func findInParentFoldersFunc(pctx *tgconfig.ParsingContext, rootdir os.Path, mod *Module) function.Function { return wrapStringSliceToStringAsFuncImpl(pctx, rootdir, mod, findInParentFoldersImpl) } @@ -57,14 +58,14 @@ func findInParentFoldersFunc(pctx *tgconfig.ParsingContext, rootdir string, mod // - The code was simplified by using Terramate project.Path. func findInParentFoldersImpl( ctx *tgconfig.ParsingContext, - rootdir string, + rootdir os.Path, mod *Module, params []string, ) (abspath string, err error) { defer func() { if err == nil { // keep track of the dependency if found successfully - mod.DependsOn = append(mod.DependsOn, project.PrjAbsPath(rootdir, abspath)) + mod.DependsOn = append(mod.DependsOn, project.PrjAbsPath(rootdir, os.NewHostPath(abspath))) } }() @@ -83,11 +84,12 @@ func findInParentFoldersImpl( fallbackParam = params[1] } - currentHostDir, err := filepath.Abs(filepath.Dir(ctx.TerragruntOptions.TerragruntConfigPath)) + currentHostDirStr, err := filepath.Abs(filepath.Dir(ctx.TerragruntOptions.TerragruntConfigPath)) if err != nil { return "", errors.WithStackTrace(err) } + currentHostDir := os.NewHostPath(currentHostDirStr) currentDir := project.PrjAbsPath(rootdir, currentHostDir) for { parentDir := currentDir.Dir() @@ -100,9 +102,9 @@ func findInParentFoldersImpl( var fileToFind string if numParams > 0 { - fileToFind = parentDir.Join(fileToFindStr).HostPath(rootdir) + fileToFind = parentDir.Join(fileToFindStr).HostPath(rootdir).String() } else { - fileToFind = tgconfig.GetDefaultConfigPath(parentDir.HostPath(rootdir)) + fileToFind = tgconfig.GetDefaultConfigPath(parentDir.HostPath(rootdir).String()) } if util.FileExists(fileToFind) { @@ -114,7 +116,7 @@ func findInParentFoldersImpl( } // readTerragruntConfigFunc implements the Terragrunt `read_terragrunt_config` function. -func readTerragruntConfigFunc(ctx *tgconfig.ParsingContext, rootdir string, mod *Module) function.Function { +func readTerragruntConfigFunc(ctx *tgconfig.ParsingContext, rootdir os.Path, mod *Module) function.Function { return function.New(&function.Spec{ // Takes one required string param Params: []function.Parameter{ @@ -152,11 +154,11 @@ func readTerragruntConfigFunc(ctx *tgconfig.ParsingContext, rootdir string, mod // Check the original version here: https://github.com/gruntwork-io/terragrunt/blob/b47b57ae0cd2c8644ca5625fceed0a2258b1a763/config/config_helpers.go#L578-L612 // The important changes are: // - The read file is added to the `mod.DependsOn` slice. -func readTerragruntConfigImpl(ctx *tgconfig.ParsingContext, configPath string, defaultVal *cty.Value, rootdir string, mod *Module) (cty.Value, error) { +func readTerragruntConfigImpl(ctx *tgconfig.ParsingContext, configPath string, defaultVal *cty.Value, rootdir os.Path, mod *Module) (cty.Value, error) { targetConfig := getCleanedTargetConfigPath(configPath, ctx.TerragruntOptions.TerragruntConfigPath) - targetConfigFileExists := util.FileExists(targetConfig) + targetConfigFileExists := util.FileExists(targetConfig.String()) if !targetConfigFileExists && defaultVal == nil { - return cty.NilVal, errors.WithStackTrace(tgconfig.TerragruntConfigNotFoundError{Path: targetConfig}) + return cty.NilVal, errors.WithStackTrace(tgconfig.TerragruntConfigNotFoundError{Path: targetConfig.String()}) } else if !targetConfigFileExists { return *defaultVal, nil } @@ -164,8 +166,8 @@ func readTerragruntConfigImpl(ctx *tgconfig.ParsingContext, configPath string, d mod.DependsOn = append(mod.DependsOn, project.PrjAbsPath(rootdir, targetConfig)) // We update the ctx of terragruntOptions to the config being read in. - ctx = ctx.WithTerragruntOptions(ctx.TerragruntOptions.Clone(targetConfig)) - cfg, err := tgconfig.ParseConfigFile(ctx.TerragruntOptions, ctx, targetConfig, nil) + ctx = ctx.WithTerragruntOptions(ctx.TerragruntOptions.Clone(targetConfig.String())) + cfg, err := tgconfig.ParseConfigFile(ctx.TerragruntOptions, ctx, targetConfig.String(), nil) if err != nil { return cty.NilVal, err } @@ -174,7 +176,7 @@ func readTerragruntConfigImpl(ctx *tgconfig.ParsingContext, configPath string, d } // readTFVarsFile reads a *.tfvars or *.tfvars.json file and returns the contents as a JSON encoded string -func readTFVarsFile(ctx *tgconfig.ParsingContext, rootdir string, mod *Module, args []string) (string, error) { +func readTFVarsFile(ctx *tgconfig.ParsingContext, rootdir os.Path, mod *Module, args []string) (string, error) { if len(args) != 1 { return "", errors.WithStackTrace(tgconfig.WrongNumberOfParamsError{Func: "read_tfvars_file", Expected: "1", Actual: len(args)}) } @@ -189,9 +191,9 @@ func readTFVarsFile(ctx *tgconfig.ParsingContext, rootdir string, mod *Module, a return "", errors.WithStackTrace(tgconfig.TFVarFileNotFoundError{File: varFile}) } - mod.DependsOn = append(mod.DependsOn, project.PrjAbsPath(rootdir, varFile)) + mod.DependsOn = append(mod.DependsOn, project.PrjAbsPath(rootdir, os.NewHostPath(varFile))) - fileContents, err := os.ReadFile(varFile) + fileContents, err := stdos.ReadFile(varFile) if err != nil { return "", errors.WithStackTrace(fmt.Errorf("could not read file %q: %w", varFile, err)) } @@ -221,7 +223,7 @@ func readTFVarsFile(ctx *tgconfig.ParsingContext, rootdir string, mod *Module, a // getCleanedTargetConfigPath returns a cleaned path to the target config (the `terragrunt.hcl` or // `terragrunt.hcl.json` file), handling relative paths correctly. This will automatically append // `terragrunt.hcl` or `terragrunt.hcl.json` to the path if the target path is a directory. -func getCleanedTargetConfigPath(configPath string, workingPath string) string { +func getCleanedTargetConfigPath(configPath string, workingPath string) os.Path { cwd := filepath.Dir(workingPath) targetConfig := configPath if !filepath.IsAbs(targetConfig) { @@ -230,7 +232,7 @@ func getCleanedTargetConfigPath(configPath string, workingPath string) string { if util.IsDir(targetConfig) { targetConfig = tgconfig.GetDefaultConfigPath(targetConfig) } - return util.CleanPath(targetConfig) + return os.NewHostPath(util.CleanPath(targetConfig)) } // wrapStringSliceToStringAsFuncImpl wraps a tgFunction and converts it into a function.Function @@ -239,7 +241,7 @@ func getCleanedTargetConfigPath(configPath string, workingPath string) string { // calls the wrapped tgFunction with the converted arguments, and returns the result as a string. func wrapStringSliceToStringAsFuncImpl( ctx *tgconfig.ParsingContext, - rootdir string, + rootdir os.Path, mod *Module, toWrap tgFunction, ) function.Function { diff --git a/tg/tg_library_sanity_test.go b/tg/tg_library_sanity_test.go index e8ac0a2c6d..9ac19293f1 100644 --- a/tg/tg_library_sanity_test.go +++ b/tg/tg_library_sanity_test.go @@ -6,8 +6,7 @@ package tg_test import ( "context" "io" - "os" - "path/filepath" + stdos "os" "testing" "github.com/google/go-cmp/cmp" @@ -16,6 +15,7 @@ import ( "github.com/gruntwork-io/terragrunt/config" "github.com/gruntwork-io/terragrunt/options" "github.com/gruntwork-io/terragrunt/util" + "github.com/terramate-io/terramate/os" . "github.com/terramate-io/terramate/test/hclwrite/hclutils" "github.com/terramate-io/terramate/test/sandbox" ) @@ -39,7 +39,7 @@ func TestTerragruntParser(t *testing.T) { { name: "empty terragrunt.hcl", want: want{ - err: os.ErrNotExist, + err: stdos.ErrNotExist, }, }, { @@ -214,7 +214,7 @@ func TestTerragruntParser(t *testing.T) { baseDir := s.RootDir() if tc.baseDir != "" { - baseDir = filepath.Join(baseDir, tc.baseDir) + baseDir = baseDir.Join(tc.baseDir) } opts := newTerragruntOptions(baseDir) @@ -238,13 +238,13 @@ func TestTerragruntParser(t *testing.T) { for k, v := range tc.want.module.FieldsMetadata { for kk, vv := range v { if str, ok := vv.(string); kk == "found_in_file" && ok { - tc.want.module.FieldsMetadata[k][kk] = filepath.Join(baseDir, str) + tc.want.module.FieldsMetadata[k][kk] = baseDir.Join(str) } } } } - got, err := config.PartialParseConfigFile(pctx, filepath.Join(baseDir, "terragrunt.hcl"), nil) + got, err := config.PartialParseConfigFile(pctx, baseDir.Join("terragrunt.hcl").String(), nil) if err != nil && tc.want.err == nil { t.Error(err) } @@ -258,12 +258,12 @@ func TestTerragruntParser(t *testing.T) { } } -func newTerragruntOptions(dir string) *options.TerragruntOptions { +func newTerragruntOptions(dir os.Path) *options.TerragruntOptions { opts := options.NewTerragruntOptions() opts.RunTerragrunt = func(_ *options.TerragruntOptions) error { return nil } - opts.WorkingDir = dir + opts.WorkingDir = dir.String() opts.Writer = io.Discard opts.ErrWriter = io.Discard opts.IgnoreExternalDependencies = true @@ -273,7 +273,7 @@ func newTerragruntOptions(dir string) *options.TerragruntOptions { // very important, otherwise the functions could block with user prompts. opts.NonInteractive = true - opts.Env = env.Parse(os.Environ()) + opts.Env = env.Parse(stdos.Environ()) if opts.DisableLogColors { util.DisableLogColors() diff --git a/tg/tg_module.go b/tg/tg_module.go index 2539ac7a9c..f6ba447f7e 100644 --- a/tg/tg_module.go +++ b/tg/tg_module.go @@ -6,7 +6,7 @@ package tg import ( "context" "io" - "os" + stdos "os" "path" "path/filepath" "sort" @@ -18,6 +18,7 @@ import ( "github.com/gruntwork-io/terragrunt/util" "github.com/rs/zerolog/log" "github.com/terramate-io/terramate/errors" + "github.com/terramate-io/terramate/os" "github.com/terramate-io/terramate/project" "github.com/zclconf/go-cty/cty/function" ) @@ -43,11 +44,11 @@ type ( // ScanModules scans dir looking for Terragrunt modules. It returns a list of // modules with its "DependsOn paths" computed. -func ScanModules(rootdir string, dir project.Path, trackDependencies bool) (Modules, error) { - absDir := project.AbsPath(rootdir, dir.String()) +func ScanModules(rootdir os.Path, dir project.Path, trackDependencies bool) (Modules, error) { + absDir := dir.HostPath(rootdir) opts := newTerragruntOptions(absDir) - tgConfigFiles, err := config.FindConfigFilesInPath(absDir, opts) + tgConfigFiles, err := config.FindConfigFilesInPath(absDir.String(), opts) if err != nil { return nil, errors.E(err, "scanning Terragrunt modules") } @@ -92,8 +93,8 @@ func ScanModules(rootdir string, dir project.Path, trackDependencies bool) (Modu pctx := config.NewParsingContext(context.Background(), cfgOpts).WithDecodeList(decodeOptions...) mod := &Module{ - Path: project.PrjAbsPath(rootdir, cfgOpts.WorkingDir), - ConfigFile: project.PrjAbsPath(rootdir, cfgfile), + Path: project.PrjAbsPath(rootdir, os.NewHostPath(cfgOpts.WorkingDir)), + ConfigFile: project.PrjAbsPath(rootdir, os.NewHostPath(cfgfile)), } // Override the predefined functions to intercept the function calls that process paths. @@ -148,7 +149,7 @@ func ScanModules(rootdir string, dir project.Path, trackDependencies bool) (Modu if !filepath.IsAbs(includedFile) { includedFile = filepath.Join(tgMod.Path, includedFile) } - dependsOn[project.PrjAbsPath(rootdir, includedFile)] = struct{}{} + dependsOn[project.PrjAbsPath(rootdir, os.NewHostPath(includedFile))] = struct{}{} } if trackDependencies { @@ -163,13 +164,13 @@ func ScanModules(rootdir string, dir project.Path, trackDependencies bool) (Modu if !filepath.IsAbs(depPath) { depPath = filepath.Join(tgMod.Path, depPath) } - dependsOn[project.PrjAbsPath(rootdir, depPath)] = struct{}{} + dependsOn[project.PrjAbsPath(rootdir, os.NewHostPath(depPath))] = struct{}{} } } for p := range dependsOn { - dependsAbsPath := project.AbsPath(rootdir, p.String()) - fileProcessed[dependsAbsPath] = struct{}{} + dependsAbsPath := rootdir.Join(p.String()) + fileProcessed[dependsAbsPath.String()] = struct{}{} mod.DependsOn = append(mod.DependsOn, p) } @@ -208,9 +209,9 @@ func ScanModules(rootdir string, dir project.Path, trackDependencies bool) (Modu return modules, nil } -func newTerragruntOptions(dir string) *options.TerragruntOptions { +func newTerragruntOptions(dir os.Path) *options.TerragruntOptions { opts := options.NewTerragruntOptions() - opts.WorkingDir = dir + opts.WorkingDir = dir.String() opts.Writer = io.Discard opts.ErrWriter = io.Discard opts.IgnoreExternalDependencies = true @@ -220,7 +221,7 @@ func newTerragruntOptions(dir string) *options.TerragruntOptions { // very important, otherwise the functions could block with user prompts. opts.NonInteractive = true - opts.Env = env.Parse(os.Environ()) + opts.Env = env.Parse(stdos.Environ()) if opts.DisableLogColors { util.DisableLogColors()