diff --git a/client/api_client.go b/client/api_client.go index cab3a195..66382e20 100644 --- a/client/api_client.go +++ b/client/api_client.go @@ -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(organizationId string, projectId string) ([]ConfigurationSet, error) ConfigurationSetDelete(id string) error ConfigurationVariablesBySetId(setId string) ([]ConfigurationVariable, error) AssignConfigurationSets(scope string, scopeId string, sets []string) error diff --git a/client/api_client_mock.go b/client/api_client_mock.go index 292c682d..0e8557ae 100644 --- a/client/api_client_mock.go +++ b/client/api_client_mock.go @@ -422,6 +422,21 @@ func (mr *MockApiClientInterfaceMockRecorder) ConfigurationSetUpdate(arg0, arg1 return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfigurationSetUpdate", reflect.TypeOf((*MockApiClientInterface)(nil).ConfigurationSetUpdate), arg0, arg1) } +// ConfigurationSets mocks base method. +func (m *MockApiClientInterface) ConfigurationSets(arg0, arg1 string) ([]ConfigurationSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ConfigurationSets", arg0, arg1) + ret0, _ := ret[0].([]ConfigurationSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ConfigurationSets indicates an expected call of ConfigurationSets. +func (mr *MockApiClientInterfaceMockRecorder) ConfigurationSets(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfigurationSets", reflect.TypeOf((*MockApiClientInterface)(nil).ConfigurationSets), arg0, arg1) +} + // ConfigurationSetsAssignments mocks base method. func (m *MockApiClientInterface) ConfigurationSetsAssignments(arg0, arg1 string) ([]ConfigurationSet, error) { m.ctrl.T.Helper() diff --git a/client/configuration_set.go b/client/configuration_set.go index 2c46dce4..157278b7 100644 --- a/client/configuration_set.go +++ b/client/configuration_set.go @@ -22,6 +22,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) { @@ -62,6 +63,26 @@ func (client *ApiClient) ConfigurationSet(id string) (*ConfigurationSet, error) return &result, nil } +func (client *ApiClient) ConfigurationSets(organizationId string, projectId string) ([]ConfigurationSet, error) { + var result []ConfigurationSet + + params := map[string]string{} + + if organizationId != "" { + params["organizationId"] = organizationId + } + + if projectId != "" { + params["projectId"] = projectId + } + + 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) } diff --git a/client/configuration_set_test.go b/client/configuration_set_test.go index aee29d76..ad7557f1 100644 --- a/client/configuration_set_test.go +++ b/client/configuration_set_test.go @@ -154,4 +154,60 @@ 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{ + "projectId": mockVariables[0].CreationScopeId, + }, gomock.Any()). + Do(func(path string, request interface{}, response *[]ConfigurationSet) { + *response = mockVariables + }).Times(1) + + variables, _ = apiClient.ConfigurationSets("", 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{ + "organizationId": mockVariables[0].CreationScopeId, + }, gomock.Any()). + Do(func(path string, request interface{}, response *[]ConfigurationSet) { + *response = mockVariables + }).Times(1) + + variables, _ = apiClient.ConfigurationSets(mockVariables[0].CreationScopeId, "") + }) + + It("Should return configuration sets", func() { + Expect(variables).To(Equal(mockVariables)) + }) + }) }) diff --git a/env0/data_variable_set.go b/env0/data_variable_set.go new file mode 100644 index 00000000..12984792 --- /dev/null +++ b/env0/data_variable_set.go @@ -0,0 +1,82 @@ +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": { + 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) + + organizationId := "" + + switch resource.Scope { + case "ORGANIZATION": + var err error + organizationId, 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") + } + } + + variableSets, err := apiClient.ConfigurationSets(organizationId, resource.ProjectId) + 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") +} diff --git a/env0/data_variable_set_test.go b/env0/data_variable_set_test.go new file mode 100644 index 00000000..d480a36f --- /dev/null +++ b/env0/data_variable_set_test.go @@ -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(organizationId string, projectId 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(organizationId, projectId).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("", 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(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("", 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("", 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) {}, + ) + }) +} diff --git a/env0/provider.go b/env0/provider.go index bd1b47b1..a5b05d96 100644 --- a/env0/provider.go +++ b/env0/provider.go @@ -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(), diff --git a/env0/resource_team_organization_assignment.go b/env0/resource_team_organization_assignment.go index 7934ade2..a011fabe 100644 --- a/env0/resource_team_organization_assignment.go +++ b/env0/resource_team_organization_assignment.go @@ -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 diff --git a/examples/data-sources/env0_variable_set/data-source.tf b/examples/data-sources/env0_variable_set/data-source.tf new file mode 100644 index 00000000..c1732018 --- /dev/null +++ b/examples/data-sources/env0_variable_set/data-source.tf @@ -0,0 +1,14 @@ +data "env0_project" "project" { + name = "my project name" +} + +data "env0_variable_set" "variable_set_project_scope" { + name = "variable set name" + scope = "PROJECT" + project_id = data.env0_project.project.id +} + +data "env0_variable_set" "variable_set_organization_scope" { + name = "variable set name" + scope = "ORGANIZATION" +} diff --git a/tests/integration/033_variable_set/main.tf b/tests/integration/033_variable_set/main.tf index a94515f2..9b8e6ab3 100644 --- a/tests/integration/033_variable_set/main.tf +++ b/tests/integration/033_variable_set/main.tf @@ -71,6 +71,21 @@ resource "env0_variable_set" "project_scope" { } } +data "env0_variable_set" "variable_set_project_scope" { + name = env0_variable_set.project_scope.name + scope = "PROJECT" + project_id = env0_project.project.id + + depends_on = [env0_variable_set.project_scope] +} + +data "env0_variable_set" "variable_set_organization_scope" { + name = env0_variable_set.org_scope.name + scope = "ORGANIZATION" + + depends_on = [env0_variable_set.org_scope] +} + resource "env0_variable_set_assignment" "assignment" { scope = "project" scope_id = env0_project.project.id