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

chore: Generate marshal json for each model #3307

Merged
merged 16 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
49 changes: 2 additions & 47 deletions pkg/acceptance/bettertestspoc/config/config.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package config

import (
"encoding/json"
"fmt"
"reflect"
"strings"
"testing"
Expand Down Expand Up @@ -67,7 +65,8 @@ func ProviderFromModel(t *testing.T, model ProviderModel) string {
return hcl
}

// FromModels allows to combine multiple models.
// FromModels should be used in terraform acceptance tests for Config attribute to get string config from all models.
// FromModels allows to combine multiple model types.
// TODO [SNOW-1501905]: introduce some common interface for all three existing models (ResourceModel, DatasourceModel, and ProviderModel)
func FromModels(t *testing.T, models ...any) string {
t.Helper()
Expand All @@ -91,50 +90,6 @@ func FromModels(t *testing.T, models ...any) string {
return sb.String()
}

// FromModel should be used in terraform acceptance tests for Config attribute to get string config from ResourceModel.
// Current implementation is really straightforward but it could be improved and tested. It may not handle all cases (like objects, lists, sets) correctly.
// TODO [SNOW-1501905]: use reflection to build config directly from model struct (or some other different way)
// TODO [SNOW-1501905]: add support for config.TestStepConfigFunc (to use as ConfigFile); the naive implementation would be to just create a tmp directory and save file there
// TODO [SNOW-1501905]: add generating MarshalJSON() function
// TODO [SNOW-1501905]: migrate resources to new config generation method (above needed first)
// Use ResourceFromModel, DatasourceFromModel, ProviderFromModel, and FromModels instead.
func FromModel(t *testing.T, model ResourceModel) string {
t.Helper()

b, err := json.Marshal(model)
require.NoError(t, err)

var objMap map[string]json.RawMessage
err = json.Unmarshal(b, &objMap)
require.NoError(t, err)

var sb strings.Builder
sb.WriteString(fmt.Sprintf(`resource "%s" "%s" {`, model.Resource(), model.ResourceName()))
sb.WriteRune('\n')
for k, v := range objMap {
sb.WriteString(fmt.Sprintf("\t%s = %s\n", k, v))
}
if len(model.DependsOn()) > 0 {
sb.WriteString(fmt.Sprintf("\tdepends_on = [%s]\n", strings.Join(model.DependsOn(), ", ")))
}
sb.WriteString(`}`)
sb.WriteRune('\n')
s := sb.String()
t.Logf("Generated config:\n%s", s)
return s
}

// FromModelsDeprecated allows to combine multiple resource models.
// Use FromModels instead.
func FromModelsDeprecated(t *testing.T, models ...ResourceModel) string {
t.Helper()
var sb strings.Builder
for _, model := range models {
sb.WriteString(FromModel(t, model) + "\n")
}
return sb.String()
}

// ConfigVariablesFromModel constructs config.Variables needed in acceptance tests that are using ConfigVariables in
// combination with ConfigDirectory. It's necessary for cases not supported by FromModel, like lists of objects.
// Use ResourceFromModel, DatasourceFromModel, ProviderFromModel, and FromModels instead.
Expand Down
10 changes: 10 additions & 0 deletions pkg/acceptance/bettertestspoc/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ resource "snowflake_share" "test" {
Item{IntField: 2, StringField: "second item"},
).
WithSingleObject("one", 2).
WithTextFieldExplicitNull().
WithListFieldEmpty().
WithMultilineField("some\nmultiline\ncontent").
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: test the behavior of \t.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll do it in one of the next PRs

WithDependsOn("some_other_resource.some_name", "other_resource.some_other_name", "third_resource.third_name")
expectedOutput := strings.TrimPrefix(`
resource "snowflake_share" "test" {
Expand All @@ -54,6 +57,13 @@ resource "snowflake_share" "test" {
a = "one"
b = 2
}
text_field = null
list_field = []
multiline_field = <<EOT
some
multiline
content
EOT
depends_on = [some_other_resource.some_name, other_resource.some_other_name, third_resource.third_name]
}
`, "\n")
Expand Down
47 changes: 39 additions & 8 deletions pkg/acceptance/bettertestspoc/config/custom_variables.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,46 @@
package config

import "encoding/json"
import (
"encoding/json"
"fmt"
)

type nullVariable struct{}
type emptyListVariable struct{}

// MarshalJSON returns the JSON encoding of nullVariable.
func (v nullVariable) MarshalJSON() ([]byte, error) {
return json.Marshal(nil)
// MarshalJSON returns the JSON encoding of emptyListVariable.
func (v emptyListVariable) MarshalJSON() ([]byte, error) {
return json.Marshal([]any{})
}

// NullVariable returns nullVariable which implements Variable.
func NullVariable() nullVariable {
return nullVariable{}
// EmptyListVariable returns Variable representing an empty list. This is because the current hcl parser handles empty SetVariable incorrectly.
func EmptyListVariable() emptyListVariable {
return emptyListVariable{}
}

type replacementPlaceholderVariable struct {
placeholder ReplacementPlaceholder
}

// MarshalJSON returns the JSON encoding of replacementPlaceholderVariable.
func (v replacementPlaceholderVariable) MarshalJSON() ([]byte, error) {
return json.Marshal(v.placeholder)
}

// ReplacementPlaceholderVariable returns Variable containing one of the ReplacementPlaceholder which is later replaced by HclFormatter.
func ReplacementPlaceholderVariable(placeholder ReplacementPlaceholder) replacementPlaceholderVariable {
return replacementPlaceholderVariable{placeholder}
}

type multilineWrapperVariable struct {
content string
}

// MarshalJSON returns the JSON encoding of multilineWrapperVariable.
func (v multilineWrapperVariable) MarshalJSON() ([]byte, error) {
return json.Marshal(fmt.Sprintf(`%[1]s%[2]s%[1]s`, SnowflakeProviderConfigMultilineMarker, v.content))
}

// MultilineWrapperVariable returns Variable containing multiline content wrapped with SnowflakeProviderConfigMultilineMarker later replaced by HclFormatter.
func MultilineWrapperVariable(content string) multilineWrapperVariable {
return multilineWrapperVariable{content}
}

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

This file was deleted.

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

Original file line number Diff line number Diff line change
@@ -1,28 +1,9 @@
package datasourcemodel

import (
"encoding/json"

tfconfig "github.com/hashicorp/terraform-plugin-testing/config"
)

// Based on https://medium.com/picus-security-engineering/custom-json-marshaller-in-go-and-common-pitfalls-c43fa774db05.
func (d *DatabasesModel) MarshalJSON() ([]byte, error) {
type Alias DatabasesModel
return json.Marshal(&struct {
*Alias
DependsOn []string `json:"depends_on,omitempty"`
}{
Alias: (*Alias)(d),
DependsOn: d.DependsOn(),
})
}

func (d *DatabasesModel) WithDependsOn(values ...string) *DatabasesModel {
d.SetDependsOn(values...)
return d
}

func (d *DatabasesModel) WithLimit(rows int) *DatabasesModel {
return d.WithLimitValue(
tfconfig.ObjectVariable(map[string]tfconfig.Variable{
Expand Down

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

Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ var (
genhelpers.SnakeCaseToCamel,
)).Parse(definitionTemplateContent)

//go:embed templates/marshal_json.tmpl
marshalJsonTemplateContent string
MarshalJsonTemplate, _ = template.New("marshalJsonTemplate").Funcs(genhelpers.BuildTemplateFuncMap(
genhelpers.FirstLetterLowercase,
genhelpers.FirstLetter,
genhelpers.SnakeCaseToCamel,
)).Parse(marshalJsonTemplateContent)

// TODO [SNOW-1501905]: consider duplicating the builders template from resource (currently same template used for datasources and provider which limits the customization possibilities for just one block type)
AllTemplates = []*template.Template{PreambleTemplate, DefinitionTemplate, resourcemodel.BuildersTemplate}
AllTemplates = []*template.Template{PreambleTemplate, DefinitionTemplate, MarshalJsonTemplate, resourcemodel.BuildersTemplate}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{{- /*gotype: github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/config/model/gen.ResourceConfigBuilderModel*/ -}}

{{- $modelName := .Name | printf "%sModel" -}}
{{- $nameLowerCase := FirstLetterLowercase .Name -}}
{{- $modelVar := FirstLetter $nameLowerCase }}
///////////////////////////////////////////////////////
// set proper json marshalling and handle depends on //
///////////////////////////////////////////////////////

func ({{ $modelVar }} *{{ $modelName }}) MarshalJSON() ([]byte, error) {
type Alias {{ $modelName }}
return json.Marshal(&struct {
*Alias
DependsOn []string `json:"depends_on,omitempty"`
}{
Alias: (*Alias)({{ $modelVar }}),
DependsOn: {{ $modelVar }}.DependsOn(),
})
}

func ({{ $modelVar }} *{{ $modelName }}) WithDependsOn(values ...string) *{{ $modelName }} {
{{ $modelVar }}.SetDependsOn(values...)
return {{ $modelVar }}
}
Loading
Loading