diff --git a/env0/data_project.go b/env0/data_project.go index 16c9d065..b72d748c 100644 --- a/env0/data_project.go +++ b/env0/data_project.go @@ -31,9 +31,15 @@ func dataProject() *schema.Resource { }, "parent_project_name": { Type: schema.TypeString, - Description: "the name of the parent project. Can be used when there are multiple subprojects with the same name under different parent projects", + Description: "the name of the parent project. Can be used as a filter when there are multiple subprojects with the same name under different parent projects", Optional: true, }, + "parent_project_id": { + Type: schema.TypeString, + Description: "the id of the parent project. Can be used as a filter when there are multiple subprojects with the same name under different parent projects", + Optional: true, + Computed: true, + }, "created_by": { Type: schema.TypeString, Description: "textual description of the entity who created the project", @@ -49,11 +55,6 @@ func dataProject() *schema.Resource { Description: "textual description of the project", Computed: true, }, - "parent_project_id": { - Type: schema.TypeString, - Description: "if the project is a sub-project, returns the parent of this sub-project", - Computed: true, - }, }, } } @@ -73,7 +74,7 @@ func dataProjectRead(ctx context.Context, d *schema.ResourceData, meta interface if !ok { return diag.Errorf("either 'name' or 'id' must be specified") } - project, err = getProjectByName(name.(string), d.Get("parent_project_name").(string), meta) + project, err = getProjectByName(name.(string), d.Get("parent_project_id").(string), d.Get("parent_project_name").(string), meta) if err != nil { return diag.Errorf("%v", err) } @@ -86,6 +87,21 @@ func dataProjectRead(ctx context.Context, d *schema.ResourceData, meta interface return nil } +func filterByParentProjectId(name string, parentId string, projects []client.Project) ([]client.Project, error) { + filteredProjects := make([]client.Project, 0) + for _, project := range projects { + if len(project.ParentProjectId) == 0 { + continue + } + + if project.ParentProjectId == parentId { + filteredProjects = append(filteredProjects, project) + } + } + + return filteredProjects, nil +} + func filterByParentProjectName(name string, parentName string, projects []client.Project, meta interface{}) ([]client.Project, error) { filteredProjects := make([]client.Project, 0) for _, project := range projects { @@ -106,7 +122,7 @@ func filterByParentProjectName(name string, parentName string, projects []client return filteredProjects, nil } -func getProjectByName(name string, parentName string, meta interface{}) (client.Project, error) { +func getProjectByName(name string, parentId string, parentName string, meta interface{}) (client.Project, error) { apiClient := meta.(client.ApiClientInterface) projects, err := apiClient.Projects() if err != nil { @@ -119,9 +135,14 @@ func getProjectByName(name string, parentName string, meta interface{}) (client. projectsByName = append(projectsByName, candidate) } } - - if len(parentName) > 0 { - // Too many results. Use parentName filter to reduce the results. + if len(parentId) > 0 { + // Use parentId filter to reduce the results. + projectsByName, err = filterByParentProjectId(name, parentId, projectsByName) + if err != nil { + return client.Project{}, err + } + } else if len(parentName) > 0 { + // Use parentName filter to reduce the results. projectsByName, err = filterByParentProjectName(name, parentName, projectsByName, meta) if err != nil { return client.Project{}, err diff --git a/env0/data_project_test.go b/env0/data_project_test.go index fb401977..c29640cb 100644 --- a/env0/data_project_test.go +++ b/env0/data_project_test.go @@ -144,6 +144,7 @@ func TestProjectDataSource(t *testing.T) { resource.TestCheckResourceAttr(accessor, "role", projectWithParent.Role), resource.TestCheckResourceAttr(accessor, "description", projectWithParent.Description), resource.TestCheckResourceAttr(accessor, "parent_project_id", projectWithParent.ParentProjectId), + resource.TestCheckResourceAttr(accessor, "parent_project_name", parentProject.Name), ), }, }, @@ -156,6 +157,30 @@ func TestProjectDataSource(t *testing.T) { ) }) + t.Run("By Name with Parent Id", func(t *testing.T) { + runUnitTest(t, + resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: dataSourceConfigCreate(resourceType, resourceName, map[string]interface{}{"name": projectWithParent.Name, "parent_project_id": parentProject.Id}), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "id", projectWithParent.Id), + resource.TestCheckResourceAttr(accessor, "name", projectWithParent.Name), + resource.TestCheckResourceAttr(accessor, "created_by", projectWithParent.CreatedBy), + resource.TestCheckResourceAttr(accessor, "role", projectWithParent.Role), + resource.TestCheckResourceAttr(accessor, "description", projectWithParent.Description), + resource.TestCheckResourceAttr(accessor, "parent_project_id", projectWithParent.ParentProjectId), + resource.TestCheckNoResourceAttr(accessor, "parent_project_name"), + ), + }, + }, + }, + func(mock *client.MockApiClientInterface) { + mock.EXPECT().Projects().AnyTimes().Return([]client.Project{projectWithParent, otherProjectWithParent}, nil) + }, + ) + }) + t.Run("Throw error when no name or id is supplied", func(t *testing.T) { runUnitTest(t, getErrorTestCase(map[string]interface{}{}, "one of `id,name` must be specified"), diff --git a/env0/resource_project.go b/env0/resource_project.go index e7744b3a..34c0d366 100644 --- a/env0/resource_project.go +++ b/env0/resource_project.go @@ -222,7 +222,7 @@ func resourceProjectImport(ctx context.Context, d *schema.ResourceData, meta int } else { tflog.Info(ctx, "Resolving project by name", map[string]interface{}{"name": id}) - if project, err = getProjectByName(id, "", meta); err != nil { + if project, err = getProjectByName(id, "", "", meta); err != nil { return nil, err } } diff --git a/examples/data-sources/env0_project/data-source.tf b/examples/data-sources/env0_project/data-source.tf index 4ba4ad7e..c2d0ad7c 100644 --- a/examples/data-sources/env0_project/data-source.tf +++ b/examples/data-sources/env0_project/data-source.tf @@ -1,3 +1,13 @@ data "env0_project" "default_project" { name = "Default Organization Project" } + +data "env0_project" "with_parent_name_filter" { + name = "Default Organization Project" + parent_project_name = "parent projet name" +} + +data "env0_project" "with_parent_id_filter" { + name = "Default Organization Project" + parent_project_id = "parent-projet-id" +} diff --git a/tests/integration/002_project/main.tf b/tests/integration/002_project/main.tf index 5b1a11b7..6dc53db8 100644 --- a/tests/integration/002_project/main.tf +++ b/tests/integration/002_project/main.tf @@ -59,6 +59,11 @@ data "env0_project" "data_by_name_with_parent_name" { parent_project_name = env0_project.test_project_other.name } +data "env0_project" "data_by_name_with_parent_id" { + name = env0_project.test_sub_project_other.name + parent_project_id = env0_project.test_project_other.id +} + output "test_project_name" { value = replace(env0_project.test_project.name, random_string.random.result, "") }