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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/repo/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func IsTemplateDir(dir string) bool {
// Older versions were incorrectly creating state.yaml file outside of template directories
// return true if any file other than state.yaml exists in .datarobot/cli
cliConfigDirPresent := slices.ContainsFunc(entries, func(entry os.DirEntry) bool {
return entry.Name() != "state.yaml"
return entry.Name() != TemplateDetectStateFileName
})

if cliConfigDirPresent {
Expand Down
4 changes: 4 additions & 0 deletions internal/repo/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ const (
QuickstartScriptPath = ".datarobot/cli/bin"
// LocalPluginDir is the project-local plugin directory relative to CWD.
LocalPluginDir = ".datarobot/cli/bin"
// TemplateDetectStateFileName is the one entry in DataRobotTemplateDetectCliPath
// that does not mark a directory as a template. Older CLI versions wrote it
// outside template directories, so its presence alone proves nothing.
TemplateDetectStateFileName = "state.yaml"
)
12 changes: 10 additions & 2 deletions internal/task/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,17 @@ func (d *Discovery) Discover(root string, maxDepth int) (string, error) {
// and return cli.ErrSilent (or the returned error directly).
func FormatDiscoveryError(err error) error {
if errors.Is(err, ErrNotInTemplate) {
return fmt.Errorf("%s\n%s",
requirement := fmt.Sprintf(
"This command requires a '%s' folder, or a '%s' folder holding something other than '%s'.",
repo.DataRobotTemplateDetectAnswersPath,
repo.DataRobotTemplateDetectCliPath,
repo.TemplateDetectStateFileName,
)

return fmt.Errorf("%s\n%s\n%s",
tui.BaseTextStyle.Render("You don't seem to be in a DataRobot Template directory."),
tui.BaseTextStyle.Render("This command requires a '.datarobot' folder to be present."))
tui.BaseTextStyle.Render(requirement),
tui.BaseTextStyle.Render("Run 'dr template setup' to create one, or switch to an existing template directory."))
}

if errors.Is(err, ErrTaskfileHasDotenv) {
Expand Down
37 changes: 37 additions & 0 deletions internal/task/discovery_test.go

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these tests are overkill, but hey! Tests are cheap these days, and we can always remove them or update them if template assumptions shift.

Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"path/filepath"
"testing"

"github.com/datarobot/cli/internal/repo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
Expand Down Expand Up @@ -180,3 +181,39 @@ func (suite *DiscoveryTestSuite) TestFindComponentsSkipsHiddenDirs() {
suite.Len(includes, 1)
suite.Equal("visible", includes[0].Name)
}

// TestFormatDiscoveryErrorNamesRealTemplateMarkers guards the ErrNotInTemplate text
// against drifting away from repo.IsTemplateDir, which is what actually gates the
// command. The previous message promised a bare '.datarobot' folder was enough.
func (suite *DiscoveryTestSuite) TestFormatDiscoveryErrorNamesRealTemplateMarkers() {
msg := FormatDiscoveryError(ErrNotInTemplate).Error()

suite.Contains(msg, repo.DataRobotTemplateDetectAnswersPath)
suite.Contains(msg, repo.DataRobotTemplateDetectCliPath)
suite.Contains(msg, repo.TemplateDetectStateFileName)
}

// TestTemplateMarkersMatchAdvertisedRequirement pins the predicate to what the
// message tells the user to do: the advertised markers must pass, and the two
// shapes the message excludes must fail.
func (suite *DiscoveryTestSuite) TestTemplateMarkersMatchAdvertisedRequirement() {
bare := filepath.Join(suite.tempDir, "bare")
suite.Require().NoError(os.MkdirAll(filepath.Join(bare, ".datarobot"), 0o755))
suite.False(repo.IsTemplateDir(bare), "a bare .datarobot folder must not satisfy the gate")

answers := filepath.Join(suite.tempDir, "answers")
suite.Require().NoError(os.MkdirAll(filepath.Join(answers, repo.DataRobotTemplateDetectAnswersPath), 0o755))
suite.True(repo.IsTemplateDir(answers), "the advertised answers folder must satisfy the gate")

stateOnly := filepath.Join(suite.tempDir, "state-only")
stateCli := filepath.Join(stateOnly, repo.DataRobotTemplateDetectCliPath)
suite.Require().NoError(os.MkdirAll(stateCli, 0o755))
suite.Require().NoError(os.WriteFile(filepath.Join(stateCli, repo.TemplateDetectStateFileName), []byte("{}"), 0o644))
suite.False(repo.IsTemplateDir(stateOnly), "state.yaml alone must not satisfy the gate")

withConfig := filepath.Join(suite.tempDir, "with-config")
configCli := filepath.Join(withConfig, repo.DataRobotTemplateDetectCliPath)
suite.Require().NoError(os.MkdirAll(configCli, 0o755))
suite.Require().NoError(os.WriteFile(filepath.Join(configCli, "config.yaml"), []byte("{}"), 0o644))
suite.True(repo.IsTemplateDir(withConfig), "cli config beyond state.yaml must satisfy the gate")
}
Loading