Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
15 changes: 15 additions & 0 deletions client/api_client_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 20 additions & 17 deletions client/project.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
package client

type Project struct {
IsArchived bool `json:"isArchived"`
OrganizationId string `json:"organizationId"`
UpdatedAt string `json:"updatedAt"`
CreatedAt string `json:"createdAt"`
Id string `json:"id"`
Name string `json:"name"`
CreatedBy string `json:"createdBy"`
Role string `json:"role"`
CreatedByUser User `json:"createdByUser"`
Description string `json:"description"`
ParentProjectId string `json:"parentProjectId,omitempty" tfschema:",omitempty"`
Hierarchy string `json:"hierarchy"`
IsArchived bool `json:"isArchived"`
OrganizationId string `json:"organizationId"`
UpdatedAt string `json:"updatedAt"`
CreatedAt string `json:"createdAt"`
Id string `json:"id"`
Name string `json:"name"`
CreatedBy string `json:"createdBy"`
Role string `json:"role"`
CreatedByUser User `json:"createdByUser"`
Description string `json:"description"`
ParentProjectId string `json:"parentProjectId,omitempty" tfschema:",omitempty"`
Hierarchy string `json:"hierarchy"`
Tags []string `json:"tags" tfschema:"tags"`
Copy link
Contributor

Choose a reason for hiding this comment

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

Run go fmt please for formatting

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Prefer adding omitempty here I think, in order to not have the tags field in the JSON appear at all if there are no tags
Same for create and update payload

Copy link
Contributor Author

@yarden-fishler-dev yarden-fishler-dev Mar 8, 2026

Choose a reason for hiding this comment

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

We've discussed this face to face, adding omitempty will also omit empty array, which might interfere with update actions that would like to remove all tags explicitly by putting [] in the request

}

type ProjectCreatePayload struct {
Name string `json:"name"`
Description string `json:"description"`
ParentProjectId string `json:"parentProjectId,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
ParentProjectId string `json:"parentProjectId,omitempty"`
Tags []string `json:"tags"`
}

type ProjectUpdatePayload struct {
Name string `json:"name"`
Description string `json:"description"`
Name string `json:"name"`
Description string `json:"description"`
Tags []string `json:"tags"`
}

type ModuleTestingProject struct {
Expand Down
3 changes: 3 additions & 0 deletions client/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var _ = Describe("Project", func() {
Name: projectName,
Description: projectDescription,
ParentProjectId: parentProjectId,
Tags: []string{"tag1", "tag2"},
},
organizationId,
}
Expand All @@ -55,6 +56,7 @@ var _ = Describe("Project", func() {
Name: projectName,
Description: projectDescription,
ParentProjectId: parentProjectId,
Tags: []string{"tag1", "tag2"},
})
})

Expand Down Expand Up @@ -95,6 +97,7 @@ var _ = Describe("Project", func() {
payload := ProjectUpdatePayload{
Name: "newName",
Description: "newDesc",
Tags: []string{"tag3"},
}

mockedResponse = mockProject
Expand Down
1 change: 1 addition & 0 deletions docs/data-sources/project.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ data "env0_project" "with_parent_id_filter" {
- `description` (String) textual description of the project
- `hierarchy` (String) the hierarchy of the project
- `role` (String) role of the authenticated user (through api key) in the project
- `tags` (List of String) tags of the project
1 change: 1 addition & 0 deletions docs/data-sources/projects.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ Read-Only:
- `is_archived` (Boolean)
- `name` (String)
- `parent_project_id` (String)
- `tags` (List of String)
2 changes: 2 additions & 0 deletions docs/resources/project.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ description: |-
resource "env0_project" "example" {
name = "example"
description = "Example project"
tags = ["team-a", "production"]
}
```

Expand All @@ -31,6 +32,7 @@ resource "env0_project" "example" {
- `description` (String) description of the project
- `force_destroy` (Boolean) Destroy the project even when environments exist
- `parent_project_id` (String) If set, the project becomes a 'sub-project' of the parent project. See https://docs.env0.com/docs/sub-projects
- `tags` (List of String) tags for the project
- `wait` (Boolean) Wait for all environments to be destroyed before destroying this project (up to 10 minutes)

### Read-Only
Expand Down
8 changes: 8 additions & 0 deletions env0/data_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ func dataProject() *schema.Resource {
Description: "the hierarchy of the project",
Computed: true,
},
"tags": {
Type: schema.TypeList,
Description: "tags of the project",
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}
Expand Down
3 changes: 3 additions & 0 deletions env0/data_project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func TestProjectDataSource(t *testing.T) {
Role: "role0",
Description: "A project's description",
ParentProjectId: "parent_id",
Tags: []string{"tag1", "tag2"},
}

archivedProject := client.Project{
Expand Down Expand Up @@ -83,6 +84,8 @@ func TestProjectDataSource(t *testing.T) {
resource.TestCheckResourceAttr(accessor, "role", project.Role),
resource.TestCheckResourceAttr(accessor, "description", project.Description),
resource.TestCheckResourceAttr(accessor, "parent_project_id", project.ParentProjectId),
resource.TestCheckResourceAttr(accessor, "tags.0", "tag1"),
resource.TestCheckResourceAttr(accessor, "tags.1", "tag2"),
),
},
},
Expand Down
8 changes: 8 additions & 0 deletions env0/data_projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ func dataProjects() *schema.Resource {
Description: "the project hierarchy (e.g. uuid1|uuid2|...)",
Computed: true,
},
"tags": {
Type: schema.TypeList,
Description: "tags of the project",
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
},
},
Expand Down
8 changes: 8 additions & 0 deletions env0/resource_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ func resourceProject() *schema.Resource {
Description: "If set, the project becomes a 'sub-project' of the parent project. See https://docs.env0.com/docs/sub-projects",
Optional: true,
},
"tags": {
Type: schema.TypeList,
Description: "tags for the project",
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}
Expand Down
69 changes: 69 additions & 0 deletions env0/resource_project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,75 @@ func TestUnitProjectResource(t *testing.T) {
})
})

projectWithTags := client.Project{
Id: "id0",
Name: "name0",
Description: "description0",
Tags: []string{"tag1", "tag2"},
}

updatedProjectWithTags := client.Project{
Id: projectWithTags.Id,
Name: "new name",
Description: "new description",
Tags: []string{"tag3"},
}

t.Run("Test project with tags", func(t *testing.T) {
testCase := resource.TestCase{
Steps: []resource.TestStep{
{
Config: resourceConfigCreate(resourceType, resourceName, map[string]any{
"name": projectWithTags.Name,
"description": projectWithTags.Description,
"tags": projectWithTags.Tags,
}),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", projectWithTags.Id),
resource.TestCheckResourceAttr(accessor, "name", projectWithTags.Name),
resource.TestCheckResourceAttr(accessor, "description", projectWithTags.Description),
resource.TestCheckResourceAttr(accessor, "tags.0", "tag1"),
resource.TestCheckResourceAttr(accessor, "tags.1", "tag2"),
),
},
{
Config: resourceConfigCreate(resourceType, resourceName, map[string]any{
"name": updatedProjectWithTags.Name,
"description": updatedProjectWithTags.Description,
"tags": updatedProjectWithTags.Tags,
}),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", updatedProjectWithTags.Id),
resource.TestCheckResourceAttr(accessor, "name", updatedProjectWithTags.Name),
resource.TestCheckResourceAttr(accessor, "description", updatedProjectWithTags.Description),
resource.TestCheckResourceAttr(accessor, "tags.0", "tag3"),
),
},
},
}

runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {
mock.EXPECT().ProjectCreate(client.ProjectCreatePayload{
Name: projectWithTags.Name,
Description: projectWithTags.Description,
Tags: projectWithTags.Tags,
}).Times(1).Return(projectWithTags, nil)
mock.EXPECT().ProjectUpdate(updatedProjectWithTags.Id, client.ProjectUpdatePayload{
Name: updatedProjectWithTags.Name,
Description: updatedProjectWithTags.Description,
Tags: updatedProjectWithTags.Tags,
}).Times(1).Return(updatedProjectWithTags, nil)

gomock.InOrder(
mock.EXPECT().Project(gomock.Any()).Times(2).Return(projectWithTags, nil), // 1 after create, 1 before update
mock.EXPECT().Project(gomock.Any()).Times(1).Return(updatedProjectWithTags, nil), // 1 after update
mock.EXPECT().ProjectEnvironments(projectWithTags.Id).Times(1).Return([]client.Environment{}, nil),
)

mock.EXPECT().ProjectDelete(projectWithTags.Id).Times(1)
})
})

t.Run("Test sub-project", func(t *testing.T) {
testCase := resource.TestCase{
Steps: []resource.TestStep{
Expand Down
4 changes: 2 additions & 2 deletions examples/resources/env0_gcp_cloud_configuration/resource.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
resource "env0_gcp_cloud_configuration" "example" {
name = "example-gcp-config"
gcp_project_id = "your-gcp-project-id"
name = "example-gcp-config"
gcp_project_id = "your-gcp-project-id"
credential_configuration_file_content = file("path/to/your-gcp-service-account.json")
}
4 changes: 2 additions & 2 deletions examples/resources/env0_project_policy/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ resource "env0_project_policy" "example" {
skip_apply_when_plan_is_empty = true
disable_destroy_environments = true
skip_redundant_deployments = true
drift_detection_cron = "0 4 * * *" # Run drift detection daily at 4 AM
auto_drift_remediation = "CODE_TO_CLOUD" # Optional, defaults to "DISABLED"
drift_detection_cron = "0 4 * * *" # Run drift detection daily at 4 AM
auto_drift_remediation = "CODE_TO_CLOUD" # Optional, defaults to "DISABLED"
}
Loading