From 4512bdf0ed7824ec6c0b0f3d117901030ef88b44 Mon Sep 17 00:00:00 2001 From: Jeremy Beard Date: Wed, 17 Jul 2024 13:21:37 -0400 Subject: [PATCH 1/2] Block dbt deploys from within Astro project --- cmd/cloud/dbt.go | 11 ++++++++++- cmd/cloud/dbt_test.go | 11 +++++++++++ config/config.go | 21 +++++++++++++++++++++ config/config_test.go | 28 ++++++++++++++++++++++++++++ config/config_test_utils.go | 27 +++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 config/config_test_utils.go diff --git a/cmd/cloud/dbt.go b/cmd/cloud/dbt.go index 67a47110e..ee87bbeb5 100644 --- a/cmd/cloud/dbt.go +++ b/cmd/cloud/dbt.go @@ -75,8 +75,17 @@ func deployDbt(cmd *cobra.Command, args []string) error { dbtProjectPath = config.WorkingPath } + // check that the dbt project path is not within an Astro project + withinAstroProject, err := config.IsWithinProjectDir(dbtProjectPath) + if err != nil { + return fmt.Errorf("failed to verify dbt project path is not within an Astro project: %w", err) + } + if withinAstroProject { + return fmt.Errorf("dbt project is within an Astro project. Use 'astro deploy' to deploy your Astro project") + } + // check that there is a valid dbt project at the dbt project path - err := validateDbtProjectExists(dbtProjectPath) + err = validateDbtProjectExists(dbtProjectPath) if err != nil { return err } diff --git a/cmd/cloud/dbt_test.go b/cmd/cloud/dbt_test.go index 5f5d85dfb..92051f969 100644 --- a/cmd/cloud/dbt_test.go +++ b/cmd/cloud/dbt_test.go @@ -11,6 +11,7 @@ import ( astroplatformcore "github.com/astronomer/astro-cli/astro-client-platform-core" astroplatformcore_mocks "github.com/astronomer/astro-cli/astro-client-platform-core/mocks" cloud "github.com/astronomer/astro-cli/cloud/deploy" + "github.com/astronomer/astro-cli/config" testUtil "github.com/astronomer/astro-cli/pkg/testing" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" @@ -125,6 +126,16 @@ func (s *DbtSuite) TestDbtDeploy_CustomMountPath() { s.mockPlatformCoreClient.AssertExpectations(s.T()) } +func (s *DbtSuite) TestDbtDeploy_WithinAstroProject() { + projectDir, cleanup, err := config.CreateTempProject() + assert.NoError(s.T(), err) + defer cleanup() + + err = testExecCmd(newDbtDeployCmd(), "test-deployment-id", "--project-path", projectDir) + assert.Error(s.T(), err) + assert.Contains(s.T(), err.Error(), "dbt project is within an Astro project") +} + func (s *DbtSuite) TestDbtDelete_PickDeployment() { s.createDbtProjectFile("dbt_project.yml") defer os.Remove("dbt_project.yml") diff --git a/config/config.go b/config/config.go index e77b15d6c..cee3be4d6 100644 --- a/config/config.go +++ b/config/config.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/astronomer/astro-cli/pkg/fileutil" "github.com/spf13/afero" @@ -230,6 +231,26 @@ func IsProjectDir(path string) (bool, error) { return fileutil.Exists(configFile, nil) } +// IsWithinProjectDir returns true if the path is at or within an Astro project directory +func IsWithinProjectDir(path string) (bool, error) { + dbtProjectPathAbs, err := filepath.Abs(filepath.Clean(path)) + if err != nil { + return false, err + } + dbtProjectPathComponents := strings.Split(dbtProjectPathAbs, string(os.PathSeparator)) + for i := range dbtProjectPathComponents { + componentAbs := strings.Join(dbtProjectPathComponents[:i+1], string(os.PathSeparator)) + isProjectDir, err := IsProjectDir(componentAbs) + if err != nil { + return false, err + } + if isProjectDir { + return true, nil + } + } + return false, nil +} + // saveConfig will save the config to a file func saveConfig(v *viper.Viper, file string) error { err := v.WriteConfigAs(file) diff --git a/config/config_test.go b/config/config_test.go index 048fc72a9..83bcb36f8 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -38,6 +38,34 @@ func (s *Suite) TestIsProjectDir() { } } +func (s *Suite) TestIsWithinProjectDir() { + projectDir, cleanupProjectDir, err := CreateTempProject() + s.NoError(err) + defer cleanupProjectDir() + + anotherDir, err := os.MkdirTemp("", "") + s.NoError(err) + + tests := []struct { + name string + in string + out bool + }{ + {"not in", anotherDir, false}, + {"at", projectDir, true}, + {"just in", filepath.Join(projectDir, "test"), true}, + {"deep in", filepath.Join(projectDir, "test", "test", "test"), true}, + {"root", string(os.PathSeparator), false}, + } + for _, tt := range tests { + s.Run(tt.name, func() { + got, err := IsWithinProjectDir(tt.in) + s.NoError(err) + s.Equal(got, tt.out) + }) + } +} + func (s *Suite) TestInitHomeDefaultCase() { fs := afero.NewMemMapFs() initHome(fs) diff --git a/config/config_test_utils.go b/config/config_test_utils.go new file mode 100644 index 000000000..55fc82f60 --- /dev/null +++ b/config/config_test_utils.go @@ -0,0 +1,27 @@ +package config + +import ( + "io/fs" + "os" + "path/filepath" +) + +func CreateTempProject() (dir string, cleanup func(), err error) { + projectDir, err := os.MkdirTemp("", "") + if err != nil { + return "", nil, err + } + astroDirPerms := 0o755 + err = os.Mkdir(filepath.Join(projectDir, ".astro"), fs.FileMode(astroDirPerms)) + if err != nil { + return "", nil, err + } + configFile, err := os.Create(filepath.Join(projectDir, ConfigDir, ConfigFileNameWithExt)) + if err != nil { + return "", nil, err + } + return projectDir, func() { + configFile.Close() + os.RemoveAll(projectDir) + }, nil +} From a24d24d288cfb390b148b59a2d0ceb325d518ce6 Mon Sep 17 00:00:00 2001 From: Jeremy Beard Date: Wed, 17 Jul 2024 13:43:27 -0400 Subject: [PATCH 2/2] Use generic var names for config util --- config/config.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/config.go b/config/config.go index cee3be4d6..acc14873c 100644 --- a/config/config.go +++ b/config/config.go @@ -233,13 +233,13 @@ func IsProjectDir(path string) (bool, error) { // IsWithinProjectDir returns true if the path is at or within an Astro project directory func IsWithinProjectDir(path string) (bool, error) { - dbtProjectPathAbs, err := filepath.Abs(filepath.Clean(path)) + pathAbs, err := filepath.Abs(filepath.Clean(path)) if err != nil { return false, err } - dbtProjectPathComponents := strings.Split(dbtProjectPathAbs, string(os.PathSeparator)) - for i := range dbtProjectPathComponents { - componentAbs := strings.Join(dbtProjectPathComponents[:i+1], string(os.PathSeparator)) + pathComponents := strings.Split(pathAbs, string(os.PathSeparator)) + for i := range pathComponents { + componentAbs := strings.Join(pathComponents[:i+1], string(os.PathSeparator)) isProjectDir, err := IsProjectDir(componentAbs) if err != nil { return false, err