Skip to content

Commit

Permalink
Fix: configuration variable that overrides a template variable in env…
Browse files Browse the repository at this point in the history
…0_environment shows drift (#955)
  • Loading branch information
TomerHeber authored Sep 25, 2024
1 parent ee882b8 commit 3bd6be6
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 160 deletions.
70 changes: 53 additions & 17 deletions client/configuration_variable.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package client

import (
"encoding/json"
"errors"
)

Expand Down Expand Up @@ -39,22 +40,30 @@ func (c *ConfigurationVariableSchema) ResourceDataSliceStructValueWrite(values m
return nil
}

type ConfigurationVariableOverwrites struct {
Value string `json:"value"`
Regex string `json:"regex"`
IsRequired bool `json:"isRequired"`
IsSensitive bool `json:"isSensitive"`
}

type ConfigurationVariable struct {
ScopeId string `json:"scopeId,omitempty"`
Value string `json:"value" tfschema:"-"`
OrganizationId string `json:"organizationId,omitempty"`
UserId string `json:"userId,omitempty"`
IsSensitive *bool `json:"isSensitive,omitempty"`
Scope Scope `json:"scope,omitempty"`
Id string `json:"id,omitempty"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Type *ConfigurationVariableType `json:"type,omitempty" tfschema:",omitempty"`
Schema *ConfigurationVariableSchema `json:"schema,omitempty"`
ToDelete *bool `json:"toDelete,omitempty"`
IsReadOnly *bool `json:"isReadonly,omitempty"`
IsRequired *bool `json:"isRequired,omitempty"`
Regex string `json:"regex,omitempty"`
ScopeId string `json:"scopeId,omitempty"`
Value string `json:"value" tfschema:"-"`
OrganizationId string `json:"organizationId,omitempty"`
UserId string `json:"userId,omitempty"`
IsSensitive *bool `json:"isSensitive,omitempty"`
Scope Scope `json:"scope,omitempty"`
Id string `json:"id,omitempty"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Type *ConfigurationVariableType `json:"type,omitempty" tfschema:",omitempty"`
Schema *ConfigurationVariableSchema `json:"schema,omitempty"`
ToDelete *bool `json:"toDelete,omitempty"`
IsReadOnly *bool `json:"isReadonly,omitempty"`
IsRequired *bool `json:"isRequired,omitempty"`
Regex string `json:"regex,omitempty"`
Overwrites *ConfigurationVariableOverwrites `json:"overwrites,omitempty"` // Is removed when marhseling to a JSON.
}

type ConfigurationVariableCreateParams struct {
Expand All @@ -77,6 +86,17 @@ type ConfigurationVariableUpdateParams struct {
Id string
}

func (v ConfigurationVariable) MarshalJSON() ([]byte, error) {
v.Overwrites = nil

// This is done to prevent an infinite loop.
type ConfigurationVariableDummy ConfigurationVariable

dummy := ConfigurationVariableDummy(v)

return json.Marshal(&dummy)
}

func (client *ApiClient) ConfigurationVariablesById(id string) (ConfigurationVariable, error) {
var result ConfigurationVariable

Expand All @@ -93,8 +113,11 @@ func (client *ApiClient) ConfigurationVariablesByScope(scope Scope, scopeId stri
if err != nil {
return nil, err
}

var result []ConfigurationVariable

params := map[string]string{"organizationId": organizationId}

switch {
case scope == ScopeGlobal:
case scope == ScopeTemplate:
Expand All @@ -111,13 +134,15 @@ func (client *ApiClient) ConfigurationVariablesByScope(scope Scope, scopeId stri
params["environmentId"] = scopeId
params["workflowEnvironmentId"] = scopeId
}

err = client.http.Get("/configuration", params, &result)
if err != nil {
return []ConfigurationVariable{}, err
}

// The API returns variables of upper scopes. Filter them out.
var filteredVariables []ConfigurationVariable

for _, variable := range result {
if scopeId == variable.ScopeId && scope == variable.Scope {
filteredVariables = append(filteredVariables, variable)
Expand All @@ -131,11 +156,14 @@ func (client *ApiClient) ConfigurationVariableCreate(params ConfigurationVariabl
if params.Scope == ScopeDeploymentLog || params.Scope == ScopeDeployment {
return ConfigurationVariable{}, errors.New("must not create variable on scope deployment / deploymentLog")
}

organizationId, err := client.OrganizationId()
if err != nil {
return ConfigurationVariable{}, err
}

var result []ConfigurationVariable

request := map[string]interface{}{
"name": params.Name,
"description": params.Description,
Expand All @@ -148,6 +176,7 @@ func (client *ApiClient) ConfigurationVariableCreate(params ConfigurationVariabl
"isReadonly": params.IsReadOnly,
"regex": params.Regex,
}

if params.Scope != ScopeGlobal {
request["scopeId"] = params.ScopeId
}
Expand All @@ -166,9 +195,11 @@ func getSchema(params ConfigurationVariableCreateParams) map[string]interface{}
schema := map[string]interface{}{
"type": "string",
}

if params.EnumValues != nil {
schema["enum"] = params.EnumValues
}

if params.Format != Text {
schema["format"] = params.Format
}
Expand All @@ -185,11 +216,14 @@ func (client *ApiClient) ConfigurationVariableUpdate(updateParams ConfigurationV
if commonParams.Scope == ScopeDeploymentLog || commonParams.Scope == ScopeDeployment {
return ConfigurationVariable{}, errors.New("must not create variable on scope deployment / deploymentLog")
}

organizationId, err := client.OrganizationId()
if err != nil {
return ConfigurationVariable{}, err
}

var result []ConfigurationVariable

request := map[string]interface{}{
"id": updateParams.Id,
"name": commonParams.Name,
Expand All @@ -203,16 +237,18 @@ func (client *ApiClient) ConfigurationVariableUpdate(updateParams ConfigurationV
"isReadonly": commonParams.IsReadOnly,
"regex": commonParams.Regex,
}

if commonParams.Scope != ScopeGlobal {
request["scopeId"] = commonParams.ScopeId
}

request["schema"] = getSchema(updateParams.CommonParams)

requestInArray := []map[string]interface{}{request}
err = client.http.Post("/configuration", requestInArray, &result)
if err != nil {

if err := client.http.Post("/configuration", requestInArray, &result); err != nil {
return ConfigurationVariable{}, err
}

return result[0], nil
}
46 changes: 42 additions & 4 deletions client/configuration_variable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package client_test

import (
"encoding/json"
"strings"
"testing"

. "github.com/env0/terraform-provider-env0/client"
"github.com/jinzhu/copier"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
)

Expand Down Expand Up @@ -52,14 +55,14 @@ var _ = Describe("Configuration Variable", func() {
Describe("Schema", func() {
It("On schema type is free text, enum should be nil", func() {
var parsedPayload ConfigurationVariable
json.Unmarshal([]byte(`{"schema": {"type": "string"}}`), &parsedPayload)
_ = json.Unmarshal([]byte(`{"schema": {"type": "string"}}`), &parsedPayload)
Expect(parsedPayload.Schema.Type).Should(Equal("string"))
Expect(parsedPayload.Schema.Enum).Should(BeNil())
})

It("On schema type is dropdown, enum should be present", func() {
var parsedPayload ConfigurationVariable
json.Unmarshal([]byte(`{"schema": {"type": "string", "enum": ["hello"]}}`), &parsedPayload)
_ = json.Unmarshal([]byte(`{"schema": {"type": "string", "enum": ["hello"]}}`), &parsedPayload)
Expect(parsedPayload.Schema.Type).Should(Equal("string"))
Expect(parsedPayload.Schema.Enum).Should(BeEquivalentTo([]string{"hello"}))
})
Expand All @@ -68,7 +71,7 @@ var _ = Describe("Configuration Variable", func() {
Describe("Enums", func() {
It("Should convert enums correctly", func() {
var parsedPayload ConfigurationVariable
json.Unmarshal([]byte(`{"scope":"PROJECT", "type": 1}`), &parsedPayload)
_ = json.Unmarshal([]byte(`{"scope":"PROJECT", "type": 1}`), &parsedPayload)
Expect(parsedPayload.Scope).Should(Equal(ScopeProject))
Expect(*parsedPayload.Type).Should(Equal(ConfigurationVariableTypeTerraform))
})
Expand Down Expand Up @@ -187,9 +190,10 @@ var _ = Describe("Configuration Variable", func() {
})

Describe("ConfigurationVariableDelete", func() {

BeforeEach(func() {
httpCall = mockHttpClient.EXPECT().Delete("configuration/"+mockConfigurationVariable.Id, nil)
apiClient.ConfigurationVariableDelete(mockConfigurationVariable.Id)
_ = apiClient.ConfigurationVariableDelete(mockConfigurationVariable.Id)
})

It("Should send DELETE request with project id", func() {
Expand Down Expand Up @@ -291,3 +295,37 @@ var _ = Describe("Configuration Variable", func() {
})
})
})

func TestConfigurationVariableMarshelling(t *testing.T) {
str := "this is a string"

variable := ConfigurationVariable{
Value: "a",
Overwrites: &ConfigurationVariableOverwrites{
Value: str,
},
}

b, err := json.Marshal(&variable)
if assert.NoError(t, err) {
assert.False(t, strings.Contains(string(b), str))
}

type ConfigurationVariableDummy ConfigurationVariable

dummy := ConfigurationVariableDummy(variable)

b, err = json.Marshal(&dummy)
if assert.NoError(t, err) {
assert.True(t, strings.Contains(string(b), str))
}

var variable2 ConfigurationVariable

err = json.Unmarshal(b, &variable2)

if assert.NoError(t, err) && assert.NotNil(t, variable2.Overwrites) {
assert.Equal(t, str, variable2.Overwrites.Value)
assert.Equal(t, variable, variable2)
}
}
2 changes: 2 additions & 0 deletions client/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

const ENVIRONMENT = "environment"

type ConfigurationVariableType int

func (c *ConfigurationVariableType) ReadResourceData(fieldName string, d *schema.ResourceData) error {
Expand Down
Loading

0 comments on commit 3bd6be6

Please sign in to comment.