From 6f4d7a605420d6fbc5cfe496512d828d229040a8 Mon Sep 17 00:00:00 2001 From: LeCrabe Date: Wed, 27 Mar 2024 16:55:38 +0100 Subject: [PATCH 1/4] feat: new helper to generate provider block for tf --- pkg/helper/provider_block.go | 54 ++++++++++++++++++++ pkg/helper/provider_block_test.go | 24 +++++++++ pkg/resources/python/resource_python_test.go | 5 +- 3 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 pkg/helper/provider_block.go create mode 100644 pkg/helper/provider_block_test.go diff --git a/pkg/helper/provider_block.go b/pkg/helper/provider_block.go new file mode 100644 index 0000000..979dc9e --- /dev/null +++ b/pkg/helper/provider_block.go @@ -0,0 +1,54 @@ +package helper + +// Provider structur +type Provider struct { + Provider string + Organisation string +} + +// New function type that accepts pointer to Provider +// (~= Signature of option functions) +type ProviderOption func(*Provider) + +// Provider constructor: +// - desc: Build a new Provider and apply specifics ProviderOption functions +// - args: ProviderOption function +// - return: pointer to Provider +func NewProvider(provider string, opts ...ProviderOption) *Provider { + // default values + const ( + defaultOrganisation = "" + ) + + p := &Provider{ + Provider: provider, + Organisation: defaultOrganisation, + } + + // ProviderOption functions + for _, opt := range opts { + opt(p) + } + + return p +} + +// Organisation name: +// - desc: concatenate function that set Provider.Organisation then return Provider +// - args: new organisation name +// - return: pointer to Provider +func (p *Provider) OrganisationName(orgName string) *Provider { + p.Organisation = orgName + return p +} + +// Provider block +// - desc: concatenate function that stringify Provider into a terraform block +// - args: none +// - return: string +func (p *Provider) String() string { + s := `provider "` + p.Provider + `" { + organisation = "` + p.Organisation + `" +}` + return s +} diff --git a/pkg/helper/provider_block_test.go b/pkg/helper/provider_block_test.go new file mode 100644 index 0000000..f55b1db --- /dev/null +++ b/pkg/helper/provider_block_test.go @@ -0,0 +1,24 @@ +package helper + +import "testing" + +func TestProvider_String(t *testing.T) { + tests := []struct { + name string + fields *Provider + want string + }{ + // TODO: Add test cases. + + {name: "test1", fields: NewProvider("clevercloud").OrganisationName("clevercloud"), want: `provider "clevercloud" { + organisation = "clevercloud" +}`}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fields.String(); got != tt.want { + t.Errorf("Provider.String() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/resources/python/resource_python_test.go b/pkg/resources/python/resource_python_test.go index c774cca..77bd69f 100644 --- a/pkg/resources/python/resource_python_test.go +++ b/pkg/resources/python/resource_python_test.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "go.clever-cloud.com/terraform-provider/pkg" + "go.clever-cloud.com/terraform-provider/pkg/helper" "go.clever-cloud.com/terraform-provider/pkg/provider/impl" "go.clever-cloud.com/terraform-provider/pkg/tmp" "go.clever-cloud.dev/client" @@ -68,7 +69,7 @@ func TestAccPython_basic(t *testing.T) { }, Steps: []resource.TestStep{{ ResourceName: rName, - Config: fmt.Sprintf(providerBlock, org) + fmt.Sprintf(pythonBlock, rName, rName), + Config: helper.NewProvider("clevercloud").OrganisationName(org).String() + fmt.Sprintf(pythonBlock, rName, rName), Check: resource.ComposeAggregateTestCheckFunc( // Test the state for provider's populated values resource.TestMatchResourceAttr(fullName, "id", regexp.MustCompile(`^app_.*$`)), @@ -148,7 +149,7 @@ func TestAccPython_basic(t *testing.T) { ), }, { ResourceName: rName2, - Config: fmt.Sprintf(providerBlock, org) + fmt.Sprintf(pythonBlock2, rName2, rName2), + Config: helper.NewProvider("clevercloud").OrganisationName(org).String() + fmt.Sprintf(pythonBlock2, rName2, rName2), Check: func(state *terraform.State) error { id := state.RootModule().Resources[fullName2].Primary.ID From d08c4477514edf0744e3b27f2fddd9d19258ce61 Mon Sep 17 00:00:00 2001 From: LeCrabe Date: Thu, 4 Apr 2024 11:49:25 +0200 Subject: [PATCH 2/4] feat: new helper to generate ressource test block --- pkg/helper/provider_block.go | 8 +- pkg/helper/provider_block_test.go | 2 +- pkg/helper/ressource_block.go | 225 +++++++++++++++++++ pkg/helper/ressource_block_test.go | 37 +++ pkg/resources/python/resource_python_test.go | 7 +- 5 files changed, 269 insertions(+), 10 deletions(-) create mode 100644 pkg/helper/ressource_block.go create mode 100644 pkg/helper/ressource_block_test.go diff --git a/pkg/helper/provider_block.go b/pkg/helper/provider_block.go index 979dc9e..42eb5b9 100644 --- a/pkg/helper/provider_block.go +++ b/pkg/helper/provider_block.go @@ -12,7 +12,7 @@ type ProviderOption func(*Provider) // Provider constructor: // - desc: Build a new Provider and apply specifics ProviderOption functions -// - args: ProviderOption function +// - args: provider name, ProviderOption function // - return: pointer to Provider func NewProvider(provider string, opts ...ProviderOption) *Provider { // default values @@ -34,16 +34,16 @@ func NewProvider(provider string, opts ...ProviderOption) *Provider { } // Organisation name: -// - desc: concatenate function that set Provider.Organisation then return Provider +// - desc: chained function that set Provider.Organisation then return Provider // - args: new organisation name // - return: pointer to Provider -func (p *Provider) OrganisationName(orgName string) *Provider { +func (p *Provider) SetOrganisation(orgName string) *Provider { p.Organisation = orgName return p } // Provider block -// - desc: concatenate function that stringify Provider into a terraform block +// - desc: chained function that stringify Provider into a terraform block // - args: none // - return: string func (p *Provider) String() string { diff --git a/pkg/helper/provider_block_test.go b/pkg/helper/provider_block_test.go index f55b1db..fd49865 100644 --- a/pkg/helper/provider_block_test.go +++ b/pkg/helper/provider_block_test.go @@ -10,7 +10,7 @@ func TestProvider_String(t *testing.T) { }{ // TODO: Add test cases. - {name: "test1", fields: NewProvider("clevercloud").OrganisationName("clevercloud"), want: `provider "clevercloud" { + {name: "test1", fields: NewProvider("clevercloud").SetOrganisation("clevercloud"), want: `provider "clevercloud" { organisation = "clevercloud" }`}, } diff --git a/pkg/helper/ressource_block.go b/pkg/helper/ressource_block.go new file mode 100644 index 0000000..5826e9d --- /dev/null +++ b/pkg/helper/ressource_block.go @@ -0,0 +1,225 @@ +package helper + +import "strconv" + +// Ressource structure + +// addon +// resource "clevercloud_addon" "%s" { +// name = "%s" +// third_party_provider = "mailpace" +// plan = "clever_solo" +// region = "par" +// } + +// cellar +// resource "clevercloud_cellar" "%s" { +// name = "%s" +// region = "par" +// } + +// cellar bucket +// resource "clevercloud_cellar_bucket" "%s" { +// id = "%s" +// cellar_id = "%s" +// } + +// postgresql +// resource "clevercloud_postgresql" "%s" { +// name = "%s" +// plan = "dev" +// region = "par" +// } + +// nodejs +// resource "clevercloud_nodejs" "%s" { +// name = "%s" +// region = "par" +// min_instance_count = 1 +// max_instance_count = 2 +// smallest_flavor = "XS" +// biggest_flavor = "M" +// redirect_https = true +// sticky_sessions = true +// app_folder = "./app" +// environment = { +// MY_KEY = "myval" +// } +// hooks { +// post_build = "echo \"build is OK!\"" +// } +// dependencies = [] +// } + +// resource "clevercloud_nodejs" "%s" { +// name = "%s" +// region = "par" +// min_instance_count = 1 +// max_instance_count = 2 +// smallest_flavor = "XS" +// biggest_flavor = "M" +// deployment { +// repository = "https://github.com/CleverCloud/nodejs-example.git" +// } +// } + +// php +// resource "clevercloud_php" "%s" { +// name = "%s" +// region = "par" +// min_instance_count = 1 +// max_instance_count = 2 +// smallest_flavor = "XS" +// biggest_flavor = "M" +// php_version = "8" +// additional_vhosts = [ "toto-tf5283457829345.com" ] +// } + +// python +// resource "clevercloud_python" "%s" { +// name = "%s" +// region = "par" +// min_instance_count = 1 +// max_instance_count = 2 +// smallest_flavor = "XS" +// biggest_flavor = "M" +// redirect_https = true +// sticky_sessions = true +// app_folder = "./app" +// python_version = "2.7" +// pip_requirements = "requirements.txt" +// environment = { +// MY_KEY = "myval" +// } +// hooks { +// post_build = "echo \"build is OK!\"" +// } +// dependencies = [] +// } + +// resource "clevercloud_python" "%s" { +// name = "%s" +// region = "par" +// min_instance_count = 1 +// max_instance_count = 2 +// smallest_flavor = "XS" +// biggest_flavor = "M" +// deployment { +// repository = "https://github.com/CleverCloud/flask-example.git" +// } +// } + +// scala +// resource "clevercloud_scala" "%s" { +// name = "%s" +// region = "par" +// min_instance_count = 1 +// max_instance_count = 2 +// smallest_flavor = "XS" +// biggest_flavor = "M" +// } + +type Ressource struct { + Ressource string + Name string + StringValues map[string]string + IntValues map[string]int +} + +// New function type that accepts pointer to Ressource +// (~= Signature of option functions) +type RessourceOption func(*Ressource) + +// Ressource constructor: +// - desc: Build a new Ressource and apply specifics RessourceOption functions +// - args: Ressource name, RessourceOption function +// - return: pointer to Ressource +func NewRessource(ressource string, opts ...RessourceOption) *Ressource { + // default values + const ( + defaultName = "" + defaultRegion = "par" + dafaultMinInstances = 1 + defaultMaxInstances = 2 + defaultSmallestFlavor = "XS" + defaultBiggestFlavor = "M" + ) + + var r Ressource + r.Ressource = ressource + r.Name = defaultName + r.StringValues = map[string]string{ + "region": defaultRegion, + "smallest_flavor": defaultSmallestFlavor, + "biggest_flavor": defaultBiggestFlavor, + } + r.IntValues = map[string]int{ + "min_instance_count": dafaultMinInstances, + "max_instance_count": defaultMaxInstances, + } + + // RessourceOption functions + for _, opt := range opts { + opt(&r) + } + + return &r +} + +// Name value setter: +// - desc: concatenate function that set Ressource.Name then return Ressource +// - args: new name +// - return: pointer to Ressource +func (r *Ressource) SetName(newName string) *Ressource { + r.Name = newName + return r +} + +// String values setter: +// - desc: set/add key: value to the string values map of a Ressource then return the Ressource +// - args: key + value +// - return: pointer to Ressource +func (p *Ressource) SetStringValues(key, value string) *Ressource { + p.StringValues[key] = value + return p +} + +// Integer values setter: +// - desc: set/add key: value to the int values map of a Ressource then return the Ressource +// - args: key + value +// - return: pointer to Ressource +func (p *Ressource) SetIntValues(key string, value int) *Ressource { + p.IntValues[key] = value + return p +} + +// Ressource block +// - desc: chained function that stringify Ressource into a terraform block +// - args: none +// - return: string +func (p *Ressource) String() string { + s := `ressource "` + p.Ressource + `" "` + p.Name + `" { + name = "` + p.Name + `" +` + // check StringValues not empty + if len(p.StringValues) != 0 { + sstring := `` + for key, value := range p.StringValues { + sstring += ` ` + key + ` = "` + value + `" +` + } + s += sstring + } + // check IntValues not empty + if len(p.IntValues) != 0 { + sint := `` + for key, value := range p.IntValues { + sint += ` ` + key + ` = ` + strconv.Itoa(value) + ` +` + } + s += sint + } + // close s + s += `}` + return s +} diff --git a/pkg/helper/ressource_block_test.go b/pkg/helper/ressource_block_test.go new file mode 100644 index 0000000..0c09360 --- /dev/null +++ b/pkg/helper/ressource_block_test.go @@ -0,0 +1,37 @@ +package helper + +import "testing" + +func TestRessource_String(t *testing.T) { + tests := []struct { + name string + fields *Ressource + want string + }{ + // TODO: Add test cases. + + {name: "test1", fields: NewRessource("clevercloud").SetName("test1"), want: `ressource "clevercloud" "test1" { + name = "test1" + region = "par" + smallest_flavor = "XS" + biggest_flavor = "M" + min_instance_count = 1 + max_instance_count = 2 +}`}, + {name: "test2", fields: NewRessource("clevercloud_python").SetName("test2").SetIntValues("min_instance_count", 3), want: `ressource "clevercloud_python" "test2" { + name = "test2" + region = "par" + smallest_flavor = "XS" + biggest_flavor = "M" + min_instance_count = 3 + max_instance_count = 2 +}`}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fields.String(); got != tt.want { + t.Errorf("Ressource.String() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/resources/python/resource_python_test.go b/pkg/resources/python/resource_python_test.go index 77bd69f..b0d0dc3 100644 --- a/pkg/resources/python/resource_python_test.go +++ b/pkg/resources/python/resource_python_test.go @@ -27,9 +27,6 @@ var pythonBlock string //go:embed resource_python_test_block2.tf var pythonBlock2 string -//go:embed provider_test_block.tf -var providerBlock string - var protoV6Provider = map[string]func() (tfprotov6.ProviderServer, error){ "clevercloud": providerserver.NewProtocol6WithError(impl.New("test")()), } @@ -69,7 +66,7 @@ func TestAccPython_basic(t *testing.T) { }, Steps: []resource.TestStep{{ ResourceName: rName, - Config: helper.NewProvider("clevercloud").OrganisationName(org).String() + fmt.Sprintf(pythonBlock, rName, rName), + Config: helper.NewProvider("clevercloud").SetOrganisation(org).String() + fmt.Sprintf(pythonBlock, rName, rName), Check: resource.ComposeAggregateTestCheckFunc( // Test the state for provider's populated values resource.TestMatchResourceAttr(fullName, "id", regexp.MustCompile(`^app_.*$`)), @@ -149,7 +146,7 @@ func TestAccPython_basic(t *testing.T) { ), }, { ResourceName: rName2, - Config: helper.NewProvider("clevercloud").OrganisationName(org).String() + fmt.Sprintf(pythonBlock2, rName2, rName2), + Config: helper.NewProvider("clevercloud").SetOrganisation(org).String() + fmt.Sprintf(pythonBlock2, rName2, rName2), Check: func(state *terraform.State) error { id := state.RootModule().Resources[fullName2].Primary.ID From 5f823d99b6ecdd9020dd620e5a1d3a1e45c89213 Mon Sep 17 00:00:00 2001 From: LeCrabe Date: Thu, 4 Apr 2024 15:04:51 +0200 Subject: [PATCH 3/4] fix(String()): sort by key the map values --- pkg/helper/ressource_block.go | 146 +++++------------------------ pkg/helper/ressource_block_test.go | 8 +- 2 files changed, 28 insertions(+), 126 deletions(-) diff --git a/pkg/helper/ressource_block.go b/pkg/helper/ressource_block.go index 5826e9d..fa37eb4 100644 --- a/pkg/helper/ressource_block.go +++ b/pkg/helper/ressource_block.go @@ -1,123 +1,9 @@ package helper -import "strconv" - -// Ressource structure - -// addon -// resource "clevercloud_addon" "%s" { -// name = "%s" -// third_party_provider = "mailpace" -// plan = "clever_solo" -// region = "par" -// } - -// cellar -// resource "clevercloud_cellar" "%s" { -// name = "%s" -// region = "par" -// } - -// cellar bucket -// resource "clevercloud_cellar_bucket" "%s" { -// id = "%s" -// cellar_id = "%s" -// } - -// postgresql -// resource "clevercloud_postgresql" "%s" { -// name = "%s" -// plan = "dev" -// region = "par" -// } - -// nodejs -// resource "clevercloud_nodejs" "%s" { -// name = "%s" -// region = "par" -// min_instance_count = 1 -// max_instance_count = 2 -// smallest_flavor = "XS" -// biggest_flavor = "M" -// redirect_https = true -// sticky_sessions = true -// app_folder = "./app" -// environment = { -// MY_KEY = "myval" -// } -// hooks { -// post_build = "echo \"build is OK!\"" -// } -// dependencies = [] -// } - -// resource "clevercloud_nodejs" "%s" { -// name = "%s" -// region = "par" -// min_instance_count = 1 -// max_instance_count = 2 -// smallest_flavor = "XS" -// biggest_flavor = "M" -// deployment { -// repository = "https://github.com/CleverCloud/nodejs-example.git" -// } -// } - -// php -// resource "clevercloud_php" "%s" { -// name = "%s" -// region = "par" -// min_instance_count = 1 -// max_instance_count = 2 -// smallest_flavor = "XS" -// biggest_flavor = "M" -// php_version = "8" -// additional_vhosts = [ "toto-tf5283457829345.com" ] -// } - -// python -// resource "clevercloud_python" "%s" { -// name = "%s" -// region = "par" -// min_instance_count = 1 -// max_instance_count = 2 -// smallest_flavor = "XS" -// biggest_flavor = "M" -// redirect_https = true -// sticky_sessions = true -// app_folder = "./app" -// python_version = "2.7" -// pip_requirements = "requirements.txt" -// environment = { -// MY_KEY = "myval" -// } -// hooks { -// post_build = "echo \"build is OK!\"" -// } -// dependencies = [] -// } - -// resource "clevercloud_python" "%s" { -// name = "%s" -// region = "par" -// min_instance_count = 1 -// max_instance_count = 2 -// smallest_flavor = "XS" -// biggest_flavor = "M" -// deployment { -// repository = "https://github.com/CleverCloud/flask-example.git" -// } -// } - -// scala -// resource "clevercloud_scala" "%s" { -// name = "%s" -// region = "par" -// min_instance_count = 1 -// max_instance_count = 2 -// smallest_flavor = "XS" -// biggest_flavor = "M" -// } +import ( + "sort" + "strconv" +) type Ressource struct { Ressource string @@ -203,18 +89,34 @@ func (p *Ressource) String() string { ` // check StringValues not empty if len(p.StringValues) != 0 { + // sort StringValues keys + tmp := make([]string, 0, len(p.StringValues)) + for k := range p.StringValues { + tmp = append(tmp, k) + } + sort.Strings(tmp) + + // create StringValues block sstring := `` - for key, value := range p.StringValues { - sstring += ` ` + key + ` = "` + value + `" + for _, k := range tmp { + sstring += ` ` + k + ` = "` + p.StringValues[k] + `" ` } s += sstring } // check IntValues not empty if len(p.IntValues) != 0 { + // sort IntValues keys + tmp := make([]string, 0, len(p.IntValues)) + for k := range p.IntValues { + tmp = append(tmp, k) + } + sort.Strings(tmp) + + // create IntValues block sint := `` - for key, value := range p.IntValues { - sint += ` ` + key + ` = ` + strconv.Itoa(value) + ` + for _, k := range tmp { + sint += ` ` + k + ` = ` + strconv.Itoa(p.IntValues[k]) + ` ` } s += sint diff --git a/pkg/helper/ressource_block_test.go b/pkg/helper/ressource_block_test.go index 0c09360..43f5b98 100644 --- a/pkg/helper/ressource_block_test.go +++ b/pkg/helper/ressource_block_test.go @@ -12,19 +12,19 @@ func TestRessource_String(t *testing.T) { {name: "test1", fields: NewRessource("clevercloud").SetName("test1"), want: `ressource "clevercloud" "test1" { name = "test1" + biggest_flavor = "M" region = "par" smallest_flavor = "XS" - biggest_flavor = "M" - min_instance_count = 1 max_instance_count = 2 + min_instance_count = 1 }`}, {name: "test2", fields: NewRessource("clevercloud_python").SetName("test2").SetIntValues("min_instance_count", 3), want: `ressource "clevercloud_python" "test2" { name = "test2" + biggest_flavor = "M" region = "par" smallest_flavor = "XS" - biggest_flavor = "M" - min_instance_count = 3 max_instance_count = 2 + min_instance_count = 3 }`}, } for _, tt := range tests { From 503b65046a7a0d1b73013314183778911fa4be49 Mon Sep 17 00:00:00 2001 From: LeCrabe Date: Thu, 4 Apr 2024 15:31:02 +0200 Subject: [PATCH 4/4] feat: setter can now takes maps --- pkg/helper/provider_block.go | 24 ++- pkg/helper/provider_block_test.go | 5 +- pkg/helper/ressource_block.go | 156 +++++++++--------- pkg/helper/ressource_block_test.go | 59 +++++-- pkg/resources/python/provider_test_block.tf | 3 - pkg/resources/python/resource_python_test.go | 41 ++++- .../python/resource_python_test_block.tf | 20 --- .../python/resource_python_test_block2.tf | 11 -- 8 files changed, 164 insertions(+), 155 deletions(-) delete mode 100644 pkg/resources/python/provider_test_block.tf delete mode 100644 pkg/resources/python/resource_python_test_block.tf delete mode 100644 pkg/resources/python/resource_python_test_block2.tf diff --git a/pkg/helper/provider_block.go b/pkg/helper/provider_block.go index 42eb5b9..e89c0a0 100644 --- a/pkg/helper/provider_block.go +++ b/pkg/helper/provider_block.go @@ -2,8 +2,8 @@ package helper // Provider structur type Provider struct { - Provider string - Organisation string + provider string + organisation string } // New function type that accepts pointer to Provider @@ -14,20 +14,15 @@ type ProviderOption func(*Provider) // - desc: Build a new Provider and apply specifics ProviderOption functions // - args: provider name, ProviderOption function // - return: pointer to Provider -func NewProvider(provider string, opts ...ProviderOption) *Provider { +func NewProvider(providerName string) *Provider { // default values const ( defaultOrganisation = "" ) p := &Provider{ - Provider: provider, - Organisation: defaultOrganisation, - } - - // ProviderOption functions - for _, opt := range opts { - opt(p) + provider: providerName, + organisation: defaultOrganisation, } return p @@ -38,7 +33,7 @@ func NewProvider(provider string, opts ...ProviderOption) *Provider { // - args: new organisation name // - return: pointer to Provider func (p *Provider) SetOrganisation(orgName string) *Provider { - p.Organisation = orgName + p.organisation = orgName return p } @@ -47,8 +42,9 @@ func (p *Provider) SetOrganisation(orgName string) *Provider { // - args: none // - return: string func (p *Provider) String() string { - s := `provider "` + p.Provider + `" { - organisation = "` + p.Organisation + `" -}` + s := `provider "` + p.provider + `" { + organisation = "` + p.organisation + `" +} +` return s } diff --git a/pkg/helper/provider_block_test.go b/pkg/helper/provider_block_test.go index fd49865..505099c 100644 --- a/pkg/helper/provider_block_test.go +++ b/pkg/helper/provider_block_test.go @@ -8,11 +8,10 @@ func TestProvider_String(t *testing.T) { fields *Provider want string }{ - // TODO: Add test cases. - {name: "test1", fields: NewProvider("clevercloud").SetOrganisation("clevercloud"), want: `provider "clevercloud" { organisation = "clevercloud" -}`}, +} +`}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/helper/ressource_block.go b/pkg/helper/ressource_block.go index fa37eb4..68b252e 100644 --- a/pkg/helper/ressource_block.go +++ b/pkg/helper/ressource_block.go @@ -1,15 +1,19 @@ package helper import ( + "reflect" "sort" "strconv" + "strings" + + "go.clever-cloud.com/terraform-provider/pkg" ) type Ressource struct { - Ressource string - Name string - StringValues map[string]string - IntValues map[string]int + ressourceType string + ressourceName string + keyValues map[string]any + blockValues map[string]any } // New function type that accepts pointer to Ressource @@ -18,31 +22,15 @@ type RessourceOption func(*Ressource) // Ressource constructor: // - desc: Build a new Ressource and apply specifics RessourceOption functions -// - args: Ressource name, RessourceOption function +// - args: Ressource type and ressource name, RessourceOption function // - return: pointer to Ressource -func NewRessource(ressource string, opts ...RessourceOption) *Ressource { - // default values - const ( - defaultName = "" - defaultRegion = "par" - dafaultMinInstances = 1 - defaultMaxInstances = 2 - defaultSmallestFlavor = "XS" - defaultBiggestFlavor = "M" - ) +func NewRessource(ressourceType, ressourceName string, opts ...RessourceOption) *Ressource { var r Ressource - r.Ressource = ressource - r.Name = defaultName - r.StringValues = map[string]string{ - "region": defaultRegion, - "smallest_flavor": defaultSmallestFlavor, - "biggest_flavor": defaultBiggestFlavor, - } - r.IntValues = map[string]int{ - "min_instance_count": dafaultMinInstances, - "max_instance_count": defaultMaxInstances, - } + r.ressourceType = ressourceType + r.ressourceName = ressourceName + r.keyValues = map[string]any{} + r.blockValues = map[string]any{} // RessourceOption functions for _, opt := range opts { @@ -52,76 +40,88 @@ func NewRessource(ressource string, opts ...RessourceOption) *Ressource { return &r } -// Name value setter: -// - desc: concatenate function that set Ressource.Name then return Ressource -// - args: new name +// unit keyValues setter: +// - desc: set/add only one key: value to keyvalues field of a Ressource then return the Ressource +// - args: key + value // - return: pointer to Ressource -func (r *Ressource) SetName(newName string) *Ressource { - r.Name = newName +func (r *Ressource) SetOneValue(key string, value any) *Ressource { + r.keyValues[key] = value return r } -// String values setter: -// - desc: set/add key: value to the string values map of a Ressource then return the Ressource -// - args: key + value -// - return: pointer to Ressource -func (p *Ressource) SetStringValues(key, value string) *Ressource { - p.StringValues[key] = value - return p +// keyValues setter: +// - desc: set/add key: value to keyValues field of a Ressource then return the Ressource +// - args: map of string key + value +// - return: RessourceOption functions +func SetKeyValues(newMap map[string]any) RessourceOption { + return func(r *Ressource) { + for key, value := range newMap { + r.keyValues[key] = value + } + } } -// Integer values setter: -// - desc: set/add key: value to the int values map of a Ressource then return the Ressource -// - args: key + value -// - return: pointer to Ressource -func (p *Ressource) SetIntValues(key string, value int) *Ressource { - p.IntValues[key] = value - return p +// blockValues setter: +// - desc: set/add key: value to kblockValues field of a Ressource then return the Ressource +// - args: map of string key + value +// - return: RessourceOption functions +func SetBlockValues(blockName string, newMap map[string]any) RessourceOption { + return func(r *Ressource) { + r.blockValues[blockName] = newMap + } } // Ressource block // - desc: chained function that stringify Ressource into a terraform block // - args: none // - return: string -func (p *Ressource) String() string { - s := `ressource "` + p.Ressource + `" "` + p.Name + `" { - name = "` + p.Name + `" +func (r *Ressource) String() string { + s := `resource "` + r.ressourceType + `" "` + r.ressourceName + `" { ` - // check StringValues not empty - if len(p.StringValues) != 0 { - // sort StringValues keys - tmp := make([]string, 0, len(p.StringValues)) - for k := range p.StringValues { - tmp = append(tmp, k) - } - sort.Strings(tmp) - // create StringValues block - sstring := `` - for _, k := range tmp { - sstring += ` ` + k + ` = "` + p.StringValues[k] + `" + // create keyValues block + s = map_String(r.keyValues, s, ` `, ` =`) + // create blockValues block + s = map_String(r.blockValues, s, ` `, ``) + + // close s + s += `} ` - } - s += sstring + + return s +} + +func map_String(m map[string]any, s, tab, separator string) string { + // sort keyValues keys + valuesKeys := make([]string, 0, len(m)) + for k := range m { + valuesKeys = append(valuesKeys, k) } - // check IntValues not empty - if len(p.IntValues) != 0 { - // sort IntValues keys - tmp := make([]string, 0, len(p.IntValues)) - for k := range p.IntValues { - tmp = append(tmp, k) - } - sort.Strings(tmp) + sort.Strings(valuesKeys) - // create IntValues block - sint := `` - for _, k := range tmp { - sint += ` ` + k + ` = ` + strconv.Itoa(p.IntValues[k]) + ` + // create keyValues block + s = pkg.Reduce(valuesKeys, s, func(acc, key string) string { + switch c_type := m[key].(type) { + case string: + var_tmp := m[key].(string) + return acc + tab + key + ` = "` + strings.ReplaceAll(var_tmp, "\"", "\\\"") + `" +` + case int: + return acc + tab + key + ` = ` + strconv.Itoa(m[key].(int)) + ` +` + case bool: + return acc + tab + key + ` = ` + strconv.FormatBool(m[key].(bool)) + ` +` + case map[string]any: + acc := acc + tab + key + separator + ` { +` + return map_String(m[key].(map[string]any), acc, ` `, separator) + tab + `} +` + default: + return acc + `// Type ` + reflect.TypeOf(c_type).String() + ` of key "` + key + `" not considered yet ` } - s += sint - } - // close s - s += `}` + }) + return s } diff --git a/pkg/helper/ressource_block_test.go b/pkg/helper/ressource_block_test.go index 43f5b98..5e8ba8d 100644 --- a/pkg/helper/ressource_block_test.go +++ b/pkg/helper/ressource_block_test.go @@ -8,24 +8,47 @@ func TestRessource_String(t *testing.T) { fields *Ressource want string }{ - // TODO: Add test cases. - - {name: "test1", fields: NewRessource("clevercloud").SetName("test1"), want: `ressource "clevercloud" "test1" { - name = "test1" - biggest_flavor = "M" - region = "par" - smallest_flavor = "XS" - max_instance_count = 2 - min_instance_count = 1 -}`}, - {name: "test2", fields: NewRessource("clevercloud_python").SetName("test2").SetIntValues("min_instance_count", 3), want: `ressource "clevercloud_python" "test2" { - name = "test2" - biggest_flavor = "M" - region = "par" - smallest_flavor = "XS" - max_instance_count = 2 - min_instance_count = 3 -}`}, + {name: "test1", + fields: NewRessource("clevercloud", "test1"), + want: `resource "clevercloud" "test1" { +} +`}, + { + name: "test2", + fields: NewRessource("clevercloud_python", "test2"). + SetOneValue("biggest_flavor", "XXL"). + SetOneValue("test", 3), + want: `resource "clevercloud_python" "test2" { + biggest_flavor = "XXL" + test = 3 +} +`}, + {name: "test3", + fields: NewRessource( + "clevercloud_python", + "test3", + SetKeyValues(map[string]any{ + "region": "ici", + "teststring": "smt", + "min_instance_count": 0, + "testint": 12, + "map": map[string]any{"test_string": "string", "test_int": 42}}), + SetBlockValues("testblock", map[string]any{"test_string": "string", "test_int": 42})), + want: `resource "clevercloud_python" "test3" { + map = { + test_int = 42 + test_string = "string" + } + min_instance_count = 0 + region = "ici" + testint = 12 + teststring = "smt" + testblock { + test_int = 42 + test_string = "string" + } +} +`}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/resources/python/provider_test_block.tf b/pkg/resources/python/provider_test_block.tf deleted file mode 100644 index ad9d8c2..0000000 --- a/pkg/resources/python/provider_test_block.tf +++ /dev/null @@ -1,3 +0,0 @@ -provider "clevercloud" { - organisation = "%s" -} diff --git a/pkg/resources/python/resource_python_test.go b/pkg/resources/python/resource_python_test.go index b0d0dc3..01e0822 100644 --- a/pkg/resources/python/resource_python_test.go +++ b/pkg/resources/python/resource_python_test.go @@ -21,12 +21,6 @@ import ( "go.clever-cloud.dev/client" ) -//go:embed resource_python_test_block.tf -var pythonBlock string - -//go:embed resource_python_test_block2.tf -var pythonBlock2 string - var protoV6Provider = map[string]func() (tfprotov6.ProviderServer, error){ "clevercloud": providerserver.NewProtocol6WithError(impl.New("test")()), } @@ -66,7 +60,28 @@ func TestAccPython_basic(t *testing.T) { }, Steps: []resource.TestStep{{ ResourceName: rName, - Config: helper.NewProvider("clevercloud").SetOrganisation(org).String() + fmt.Sprintf(pythonBlock, rName, rName), + Config: helper.NewProvider("clevercloud"). + SetOrganisation(org).String() + helper.NewRessource( + "clevercloud_python", + rName, + helper.SetKeyValues(map[string]any{ + "name": rName, + "region": "par", + "min_instance_count": 1, + "max_instance_count": 2, + "smallest_flavor": "XS", + "biggest_flavor": "M", + "redirect_https": true, + "sticky_sessions": true, + "app_folder": "./app", + "python_version": "2.7", + "pip_requirements": "requirements.txt", + "environment": map[string]any{ + "MY_KEY": "myval", + }, + }), + helper.SetBlockValues("hooks", map[string]any{"post_build": "echo \"build is OK!\""}), + ).String(), Check: resource.ComposeAggregateTestCheckFunc( // Test the state for provider's populated values resource.TestMatchResourceAttr(fullName, "id", regexp.MustCompile(`^app_.*$`)), @@ -146,7 +161,17 @@ func TestAccPython_basic(t *testing.T) { ), }, { ResourceName: rName2, - Config: helper.NewProvider("clevercloud").SetOrganisation(org).String() + fmt.Sprintf(pythonBlock2, rName2, rName2), + Config: helper.NewProvider("clevercloud").SetOrganisation(org).String() + helper.NewRessource("clevercloud_python", + rName2, + helper.SetKeyValues(map[string]any{ + "name": "%s", + "region": "par", + "min_instance_count": 1, + "max_instance_count": 2, + "smallest_flavor": "XS", + "biggest_flavor": "M", + }), + helper.SetBlockValues("deployment", map[string]any{"repository": "https://github.com/CleverCloud/flask-example.git"})).String(), Check: func(state *terraform.State) error { id := state.RootModule().Resources[fullName2].Primary.ID diff --git a/pkg/resources/python/resource_python_test_block.tf b/pkg/resources/python/resource_python_test_block.tf deleted file mode 100644 index c61f2ad..0000000 --- a/pkg/resources/python/resource_python_test_block.tf +++ /dev/null @@ -1,20 +0,0 @@ -resource "clevercloud_python" "%s" { - name = "%s" - region = "par" - min_instance_count = 1 - max_instance_count = 2 - smallest_flavor = "XS" - biggest_flavor = "M" - redirect_https = true - sticky_sessions = true - app_folder = "./app" - python_version = "2.7" - pip_requirements = "requirements.txt" - environment = { - MY_KEY = "myval" - } - hooks { - post_build = "echo \"build is OK!\"" - } - dependencies = [] -} diff --git a/pkg/resources/python/resource_python_test_block2.tf b/pkg/resources/python/resource_python_test_block2.tf deleted file mode 100644 index 8569928..0000000 --- a/pkg/resources/python/resource_python_test_block2.tf +++ /dev/null @@ -1,11 +0,0 @@ -resource "clevercloud_python" "%s" { - name = "%s" - region = "par" - min_instance_count = 1 - max_instance_count = 2 - smallest_flavor = "XS" - biggest_flavor = "M" - deployment { - repository = "https://github.com/CleverCloud/flask-example.git" - } -}