Skip to content

Commit

Permalink
Feat: add env0_projects (plural) data resource (#787)
Browse files Browse the repository at this point in the history
* Feat: add env0_projects (plural) data resource

* added more features

* fix typo
  • Loading branch information
TomerHeber committed Feb 2, 2024
1 parent 77bc1b6 commit b9c02fd
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 0 deletions.
1 change: 1 addition & 0 deletions client/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type Project struct {
CreatedByUser User `json:"createdByUser"`
Description string `json:"description"`
ParentProjectId string `json:"parentProjectId,omitempty" tfschema:",omitempty"`
Hierarchy string `json:"hierarchy"`
}

type ProjectCreatePayload struct {
Expand Down
5 changes: 5 additions & 0 deletions env0/data_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ func dataProject() *schema.Resource {
Description: "textual description of the project",
Computed: true,
},
"hierarchy": {
Type: schema.TypeString,
Description: "the hierarchy of the project",
Computed: true,
},
},
}
}
Expand Down
84 changes: 84 additions & 0 deletions env0/data_projects.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package env0

import (
"context"

"github.com/env0/terraform-provider-env0/client"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func dataProjects() *schema.Resource {
return &schema.Resource{
ReadContext: dataProjectsRead,

Schema: map[string]*schema.Schema{
"include_archived_projects": {
Type: schema.TypeBool,
Description: "set to 'true' to include archived projects (defaults to 'false')",
Optional: true,
Default: false,
},
"projects": {
Type: schema.TypeList,
Description: "list of projects",
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Description: "the name of the project",
Computed: true,
},
"id": {
Type: schema.TypeString,
Description: "id of the project",
Computed: true,
},
"is_archived": {
Type: schema.TypeBool,
Description: "'true' if the project is archived",
Computed: true,
},
"parent_project_id": {
Type: schema.TypeString,
Description: "the parent project id (if one exist)",
Computed: true,
},
"hierarchy": {
Type: schema.TypeString,
Description: "the project hierarchy (e.g. uuid1|uuid2|...)",
Computed: true,
},
},
},
},
},
}
}

func dataProjectsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
apiClient := meta.(client.ApiClientInterface)

includeArchivedProjects := d.Get("include_archived_projects").(bool)

projects, err := apiClient.Projects()
if err != nil {
return diag.Errorf("failed to get list of projects: %v", err)
}

filteredProjects := []client.Project{}
for _, project := range projects {
if includeArchivedProjects || !project.IsArchived {
filteredProjects = append(filteredProjects, project)
}
}

if err := writeResourceDataSlice(filteredProjects, "projects", d); err != nil {
return diag.Errorf("schema slice resource data serialization failed: %v", err)
}

d.SetId("projects")

return nil
}
127 changes: 127 additions & 0 deletions env0/data_projects_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package env0

import (
"errors"
"regexp"
"testing"

"github.com/env0/terraform-provider-env0/client"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestProjectsDataSource(t *testing.T) {
project1 := client.Project{
Id: "id0",
Name: "my-project-1",
ParentProjectId: "p1",
Hierarchy: "adsas|fdsfsd",
}

project2 := client.Project{
Id: "id1",
Name: "my-project-2",
}

project3 := client.Project{
Id: "id1",
Name: "my-project-2",
IsArchived: true,
}

projects := []client.Project{project1, project2, project3}

resourceType := "env0_projects"
resourceName := "test_projects"
accessor := dataSourceAccessor(resourceType, resourceName)

getValidTestCase := func() resource.TestCase {
return resource.TestCase{
Steps: []resource.TestStep{
{
Config: dataSourceConfigCreate(resourceType, resourceName, map[string]interface{}{}),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "projects.0.id", project1.Id),
resource.TestCheckResourceAttr(accessor, "projects.0.name", project1.Name),
resource.TestCheckResourceAttr(accessor, "projects.0.parent_project_id", project1.ParentProjectId),
resource.TestCheckResourceAttr(accessor, "projects.0.hierarchy", project1.Hierarchy),
resource.TestCheckResourceAttr(accessor, "projects.0.is_archived", "false"),
resource.TestCheckResourceAttr(accessor, "projects.1.id", project2.Id),
resource.TestCheckResourceAttr(accessor, "projects.1.name", project2.Name),
resource.TestCheckResourceAttr(accessor, "projects.1.is_archived", "false"),
resource.TestCheckResourceAttr(accessor, "projects.#", "2"),
),
},
},
}
}

getValidTestCaseWithArchived := func() resource.TestCase {
return resource.TestCase{
Steps: []resource.TestStep{
{
Config: dataSourceConfigCreate(resourceType, resourceName, map[string]interface{}{
"include_archived_projects": "true",
}),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "projects.0.id", project1.Id),
resource.TestCheckResourceAttr(accessor, "projects.0.name", project1.Name),
resource.TestCheckResourceAttr(accessor, "projects.0.parent_project_id", project1.ParentProjectId),
resource.TestCheckResourceAttr(accessor, "projects.0.hierarchy", project1.Hierarchy),
resource.TestCheckResourceAttr(accessor, "projects.0.is_archived", "false"),
resource.TestCheckResourceAttr(accessor, "projects.1.id", project2.Id),
resource.TestCheckResourceAttr(accessor, "projects.1.name", project2.Name),
resource.TestCheckResourceAttr(accessor, "projects.1.is_archived", "false"),
resource.TestCheckResourceAttr(accessor, "projects.2.id", project2.Id),
resource.TestCheckResourceAttr(accessor, "projects.2.name", project2.Name),
resource.TestCheckResourceAttr(accessor, "projects.2.is_archived", "true"),
resource.TestCheckResourceAttr(accessor, "projects.#", "3"),
),
},
},
}
}

getErrorTestCase := func(expectedError string) resource.TestCase {
return resource.TestCase{
Steps: []resource.TestStep{
{
Config: dataSourceConfigCreate(resourceType, resourceName, map[string]interface{}{}),
ExpectError: regexp.MustCompile(expectedError),
},
},
}
}

mockGetProjectsCall := func(returnValue []client.Project) func(mockFunc *client.MockApiClientInterface) {
return func(mock *client.MockApiClientInterface) {
mock.EXPECT().Projects().AnyTimes().Return(returnValue, nil)
}
}

mockGetProjectsCallFailed := func(err string) func(mockFunc *client.MockApiClientInterface) {
return func(mock *client.MockApiClientInterface) {
mock.EXPECT().Projects().AnyTimes().Return([]client.Project{}, errors.New(err))
}
}

t.Run("get all projects", func(t *testing.T) {
runUnitTest(t,
getValidTestCase(),
mockGetProjectsCall(projects),
)
})

t.Run("get all projects including archived", func(t *testing.T) {
runUnitTest(t,
getValidTestCaseWithArchived(),
mockGetProjectsCall(projects),
)
})

t.Run("Error when API call fails", func(t *testing.T) {
runUnitTest(t,
getErrorTestCase("failed to get list of projects: error"),
mockGetProjectsCallFailed("error"),
)
})
}
1 change: 1 addition & 0 deletions env0/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ func Provider(version string) plugin.ProviderFunc {
"env0_gpg_key": dataGpgKey(),
"env0_provider": dataProvider(),
"env0_custom_flow": dataCustomFlow(),
"env0_projects": dataProjects(),
},
ResourcesMap: map[string]*schema.Resource{
"env0_project": resourceProject(),
Expand Down
1 change: 1 addition & 0 deletions examples/data-sources/env0_projects/data-source.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
data "env0_projects" "list_of_projects" {}
2 changes: 2 additions & 0 deletions tests/integration/002_project/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ data "env0_project" "data_by_name_with_parent_id" {
parent_project_id = env0_project.test_project_other.id
}

data "env0_projects" "list_of_projects" {}

output "test_project_name" {
value = replace(env0_project.test_project.name, random_string.random.result, "")
}
Expand Down

0 comments on commit b9c02fd

Please sign in to comment.