Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: add data source for env0_variable_set #897

Merged
merged 5 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions client/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ type ApiClientInterface interface {
ConfigurationSetCreate(payload *CreateConfigurationSetPayload) (*ConfigurationSet, error)
ConfigurationSetUpdate(id string, payload *UpdateConfigurationSetPayload) (*ConfigurationSet, error)
ConfigurationSet(id string) (*ConfigurationSet, error)
ConfigurationSets(scope string, scopeId string) ([]ConfigurationSet, error)
ConfigurationSetDelete(id string) error
ConfigurationVariablesBySetId(setId string) ([]ConfigurationVariable, error)
AssignConfigurationSets(scope string, scopeId string, sets []string) error
Expand Down
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.

21 changes: 20 additions & 1 deletion client/configuration_set.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package client

import "fmt"
import (
"fmt"
"strings"
)

type CreateConfigurationSetPayload struct {
Name string `json:"name"`
Expand All @@ -22,6 +25,7 @@ type ConfigurationSet struct {
Name string `json:"name"`
Description string `json:"description"`
AssignmentScope string `json:"assignmentScope"`
CreationScopeId string `json:"creationScopeId"`
}

func (client *ApiClient) ConfigurationSetCreate(payload *CreateConfigurationSetPayload) (*ConfigurationSet, error) {
Expand Down Expand Up @@ -62,6 +66,21 @@ func (client *ApiClient) ConfigurationSet(id string) (*ConfigurationSet, error)
return &result, nil
}

func (client *ApiClient) ConfigurationSets(scope string, scopeId string) ([]ConfigurationSet, error) {
var result []ConfigurationSet

params := map[string]string{
"scope": strings.ToLower(scope),
"scopeId": scopeId,
}

if err := client.http.Get("/configuration-sets", params, &result); err != nil {
return nil, err
}

return result, nil
}

func (client *ApiClient) ConfigurationSetDelete(id string) error {
return client.http.Delete("/configuration-sets/"+id, nil)
}
Expand Down
58 changes: 58 additions & 0 deletions client/configuration_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,62 @@ var _ = Describe("Configuration Set", func() {
Expect(variables).To(Equal(mockVariables))
})
})

Describe("get configuration variables by set project id", func() {
mockVariables := []ConfigurationSet{
{
Id: "id",
Name: "name",
CreationScopeId: "create_scope_id",
},
}

var variables []ConfigurationSet

BeforeEach(func() {
httpCall = mockHttpClient.EXPECT().
Get("/configuration-sets", map[string]string{
"scopeId": mockVariables[0].CreationScopeId,
"scope": "project",
}, gomock.Any()).
Do(func(path string, request interface{}, response *[]ConfigurationSet) {
*response = mockVariables
}).Times(1)

variables, _ = apiClient.ConfigurationSets("PROJECT", mockVariables[0].CreationScopeId)
})

It("Should return configuration sets", func() {
Expect(variables).To(Equal(mockVariables))
})
})

Describe("get configuration variables by set organization id", func() {
mockVariables := []ConfigurationSet{
{
Id: "id",
Name: "name",
CreationScopeId: "create_scope_id",
},
}

var variables []ConfigurationSet

BeforeEach(func() {
httpCall = mockHttpClient.EXPECT().
Get("/configuration-sets", map[string]string{
"scopeId": mockVariables[0].CreationScopeId,
"scope": "organization",
}, gomock.Any()).
Do(func(path string, request interface{}, response *[]ConfigurationSet) {
*response = mockVariables
}).Times(1)

variables, _ = apiClient.ConfigurationSets("ORGANIZATION", mockVariables[0].CreationScopeId)
})

It("Should return configuration sets", func() {
Expect(variables).To(Equal(mockVariables))
})
})
})
83 changes: 83 additions & 0 deletions env0/data_variable_set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
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 dataVariableSet() *schema.Resource {
return &schema.Resource{
ReadContext: dataVariableSetRead,

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Description: "the name of the variable set",
Required: true,
},
"scope": {

Choose a reason for hiding this comment

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

I think calling this creationScope is clearer, will also match our API

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we could do "creation_scope".
However, in all other places in the provider, it's scope.
So I prefer to keep it aligned.

Choose a reason for hiding this comment

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

Alright, I already approved regardless 😄

Type: schema.TypeString,
Description: "the scope of the variable set. Valid values: 'ORGANIZATION', or 'PROJECT'",
Required: true,
ValidateDiagFunc: NewStringInValidator([]string{"ORGANIZATION", "PROJECT"}),
},
"project_id": {
Type: schema.TypeString,
Description: "the id of the 'PROJECT' scope. Is not required for 'ORGANIZATION' scope",
Optional: true,
},
"id": {
Type: schema.TypeString,
Description: "the id variable set",
Computed: true,
},
},
}
}

func dataVariableSetRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
resource := struct {
Name string
Scope string
ProjectId string
}{}

if err := readResourceData(&resource, d); err != nil {
return diag.Errorf("schema resource data deserialization failed: %v", err)
}

apiClient := meta.(client.ApiClientInterface)

var scopeId string

switch resource.Scope {
case "ORGANIZATION":
var err error
scopeId, err = apiClient.OrganizationId()
if err != nil {
return diag.Errorf("could not get organization id: %v", err)
}
case "PROJECT":
if resource.ProjectId == "" {
return diag.Errorf("'project_id' is required")
}
scopeId = resource.ProjectId
}

variableSets, err := apiClient.ConfigurationSets(resource.Scope, scopeId)
if err != nil {
return diag.Errorf("could not get variable sets: %v", err)
}

for _, variableSet := range variableSets {
if variableSet.Name == resource.Name {
d.SetId(variableSet.Id)
return nil
}
}

return diag.Errorf("variable set not found")
}
158 changes: 158 additions & 0 deletions env0/data_variable_set_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package env0

import (
"errors"
"regexp"
"testing"

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

func TestVariableSetDataSource(t *testing.T) {
projectId := "project_id"
organizationId := "organization_id"

v1 := client.ConfigurationSet{
Id: "id1",
Name: "name1",
CreationScopeId: projectId,
}

v2 := client.ConfigurationSet{
Id: "id2",
Name: "name2",
CreationScopeId: projectId,
}

v3 := client.ConfigurationSet{
Id: "id3",
Name: "name3",
CreationScopeId: organizationId,
}

v4 := client.ConfigurationSet{
Id: "id4",
Name: "name4",
CreationScopeId: "some_other_id",
}

resourceType := "env0_variable_set"
resourceName := "test_variable_set"
accessor := dataSourceAccessor(resourceType, resourceName)

getConfig := func(name string, scope string, projectId string) string {
fields := map[string]interface{}{"name": name, "scope": scope}
if projectId != "" {
fields["project_id"] = projectId
}
return dataSourceConfigCreate(resourceType, resourceName, fields)
}

mockVariableSetsCall := func(scope string, scopeId string, returnValue []client.ConfigurationSet) func(mockFunc *client.MockApiClientInterface) {
return func(mock *client.MockApiClientInterface) {
if organizationId != "" {
mock.EXPECT().OrganizationId().AnyTimes().Return(organizationId, nil)
}
mock.EXPECT().ConfigurationSets(scope, scopeId).AnyTimes().Return(returnValue, nil)
}
}

t.Run("project id scope", func(t *testing.T) {
runUnitTest(t,
resource.TestCase{
Steps: []resource.TestStep{
{
Config: getConfig(v2.Name, "PROJECT", v2.CreationScopeId),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", v2.Id),
),
},
},
},
mockVariableSetsCall("PROJECT", projectId, []client.ConfigurationSet{
v4, v1, v2,
}),
)
})

t.Run("organization id scope", func(t *testing.T) {
runUnitTest(t,
resource.TestCase{
Steps: []resource.TestStep{
{
Config: getConfig(v3.Name, "ORGANIZATION", ""),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", v3.Id),
),
},
},
},
mockVariableSetsCall("ORGANIZATION", organizationId, []client.ConfigurationSet{
v4, v3,
}),
)
})

t.Run("name not found", func(t *testing.T) {
runUnitTest(t,
resource.TestCase{
Steps: []resource.TestStep{
{
Config: getConfig("name that isn't found", "PROJECT", v2.CreationScopeId),
ExpectError: regexp.MustCompile("variable set not found"),
},
},
},
mockVariableSetsCall("PROJECT", projectId, []client.ConfigurationSet{
v4, v1, v2, v3,
}),
)
})

t.Run("get configuration sets api call failed", func(t *testing.T) {
runUnitTest(t,
resource.TestCase{
Steps: []resource.TestStep{
{
Config: getConfig(v2.Name, "PROJECT", v2.CreationScopeId),
ExpectError: regexp.MustCompile("could not get variable sets: error"),
},
},
},
func(mock *client.MockApiClientInterface) {
mock.EXPECT().ConfigurationSets("PROJECT", projectId).AnyTimes().Return(nil, errors.New("error"))
},
)
})

t.Run("get organization id api call failed", func(t *testing.T) {
runUnitTest(t,
resource.TestCase{
Steps: []resource.TestStep{
{
Config: getConfig(v3.Name, "ORGANIZATION", ""),
ExpectError: regexp.MustCompile("could not get organization id: error"),
},
},
},
func(mock *client.MockApiClientInterface) {
mock.EXPECT().OrganizationId().AnyTimes().Return("", errors.New("error"))
},
)
})

t.Run("project_id is required", func(t *testing.T) {
runUnitTest(t,
resource.TestCase{
Steps: []resource.TestStep{
{
Config: getConfig(v2.Name, "PROJECT", ""),
ExpectError: regexp.MustCompile("'project_id' is required"),
},
},
},
func(mock *client.MockApiClientInterface) {},
)
})
}
1 change: 1 addition & 0 deletions env0/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ func Provider(version string) plugin.ProviderFunc {
"env0_custom_flow": dataCustomFlow(),
"env0_projects": dataProjects(),
"env0_module_testing_project": dataModuleTestingProject(),
"env0_variable_set": dataVariableSet(),
},
ResourcesMap: map[string]*schema.Resource{
"env0_project": resourceProject(),
Expand Down
1 change: 0 additions & 1 deletion env0/resource_team_organization_assignment.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ func resourceTeamOrganizationAssignmentCreateOrUpdate(ctx context.Context, d *sc
organizationId, err := apiClient.OrganizationId()
if err != nil {
return diag.Errorf("could not get organization id: %v", err)

}

var payload client.TeamRoleAssignmentCreateOrUpdatePayload
Expand Down
Loading
Loading