From 80f216c66b6c0e8ced5a19b2020774ad99f3220d Mon Sep 17 00:00:00 2001 From: Eugene Yarshevich Date: Tue, 18 Apr 2023 14:20:18 +0300 Subject: [PATCH 1/5] feat: added resource annotations support Signed-off-by: Eugene Yarshevich --- go.mod | 4 +- go.sum | 8 ++-- internal/humanitec/convert.go | 61 +++++++++++++++++--------- internal/humanitec/convert_test.go | 13 ++++++ internal/humanitec/extensions/types.go | 10 +++-- internal/humanitec/templates.go | 8 +++- 6 files changed, 74 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 1974ec5..b6e38c8 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,10 @@ require ( github.com/golang/mock v1.6.0 github.com/imdario/mergo v0.3.13 github.com/mitchellh/mapstructure v1.5.0 - github.com/score-spec/score-go v0.0.0-20221019054335-3510902b5f8b + github.com/score-spec/score-go v0.0.0-20230417150859-c1bf3fbe372b github.com/sendgrid/rest v2.6.9+incompatible github.com/spf13/cobra v1.6.0 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.1 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index ab96424..d7f0e40 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/score-spec/score-go v0.0.0-20221019054335-3510902b5f8b h1:Ws6TNwu+OuoR+K7C3fKLCuaeyAiGU0G7Xkaz9UUygWA= -github.com/score-spec/score-go v0.0.0-20221019054335-3510902b5f8b/go.mod h1:eNU0evgibNfV6ESUfRKjWcfGPmd92dI8dsUN/GBouZs= +github.com/score-spec/score-go v0.0.0-20230417150859-c1bf3fbe372b h1:s6DuDF4QC/jjXvBYYJfYKaRgsTGc3dTNwbT43eJCC04= +github.com/score-spec/score-go v0.0.0-20230417150859-c1bf3fbe372b/go.mod h1:eNU0evgibNfV6ESUfRKjWcfGPmd92dI8dsUN/GBouZs= github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0= github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE= github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= @@ -23,9 +23,11 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/internal/humanitec/convert.go b/internal/humanitec/convert.go index b217dd9..7946db1 100644 --- a/internal/humanitec/convert.go +++ b/internal/humanitec/convert.go @@ -9,6 +9,7 @@ package humanitec import ( "fmt" + "log" "strings" mergo "github.com/imdario/mergo" @@ -17,6 +18,10 @@ import ( humanitec "github.com/score-spec/score-humanitec/internal/humanitec_go/types" ) +const ( + ResourceScopeAnnotationLabel = "humanitec.io/scope" +) + // getProbeDetails extracts an httpGet probe details from the source spec. // Returns nil if the source spec is empty. func getProbeDetails(probe *score.ContainerProbeSpec) map[string]interface{} { @@ -157,13 +162,43 @@ func ConvertSpec(name, envID string, spec *score.WorkloadSpec, ext *extensions.H "spec": workloadSpec, } - var externals = map[string]interface{}{} + var externals = make(map[string]interface{}) + var shared = make([]humanitec.UpdateAction, 0) for name, res := range spec.Resources { - if meta, exists := ext.Resources[name]; !exists || meta.Scope == "" || meta.Scope == "external" { - if res.Type != "service" && res.Type != "environment" { + switch res.Type { + + case "service", "environment": + continue + + default: + scope, hasAnnotation := res.Metadata.Annotations[ResourceScopeAnnotationLabel] + // DEPRECATED: Should use resource annotations instead + if meta, hasMeta := ext.Resources[name]; hasMeta { + log.Printf("Warning: Extensions for resources has been deprecated. Use Score resource annotations instead. Extensions are stil configured for '%s'.\n", name) + if !hasAnnotation { + scope = meta.Scope + } + } + // END (DEPRECATED) + + switch scope { + + case "", "external": externals[name] = map[string]interface{}{ "type": res.Type, } + + case "shared": + shared = append(shared, humanitec.UpdateAction{ + Operation: "add", + Path: "/" + name, + Value: map[string]interface{}{ + "type": res.Type, + }, + }) + + default: + log.Printf("Warning: Invalid scope value '%s' for resource '%s'. Not supported.\n", scope, name) } } } @@ -171,22 +206,6 @@ func ConvertSpec(name, envID string, spec *score.WorkloadSpec, ext *extensions.H workload["externals"] = externals } - var shared []humanitec.UpdateAction - for name, res := range spec.Resources { - if meta, exists := ext.Resources[name]; exists && meta.Scope == "shared" { - if shared == nil { - shared = make([]humanitec.UpdateAction, 0) - } - shared = append(shared, humanitec.UpdateAction{ - Operation: "add", - Path: "/" + name, - Value: map[string]interface{}{ - "type": res.Type, - }, - }) - } - } - var res = humanitec.CreateDeploymentDeltaRequest{ Metadata: humanitec.DeltaMetadata{ Name: name, @@ -197,7 +216,9 @@ func ConvertSpec(name, envID string, spec *score.WorkloadSpec, ext *extensions.H spec.Metadata.Name: workload, }, }, - Shared: shared, + } + if len(shared) > 0 { + res.Shared = shared } return &res, nil diff --git a/internal/humanitec/convert_test.go b/internal/humanitec/convert_test.go index 4d386d5..1d54181 100644 --- a/internal/humanitec/convert_test.go +++ b/internal/humanitec/convert_test.go @@ -191,6 +191,11 @@ func TestScoreConvert(t *testing.T) { }, Resources: map[string]score.ResourceSpec{ "env": { + Metadata: score.ResourceMeta{ + Annotations: map[string]string{ + ResourceScopeAnnotationLabel: "external", + }, + }, Type: "environment", Properties: map[string]score.ResourcePropertySpec{ "DEBUG": {Default: false, Required: false}, @@ -207,6 +212,11 @@ func TestScoreConvert(t *testing.T) { Type: "volume", }, "db": { + Metadata: score.ResourceMeta{ + Annotations: map[string]string{ + ResourceScopeAnnotationLabel: "external", + }, + }, Type: "postgres", Properties: map[string]score.ResourcePropertySpec{ "host": {Default: "localhost", Required: true}, @@ -245,6 +255,9 @@ func TestScoreConvert(t *testing.T) { }, }, Resources: extensions.HumanitecResourcesSpecs{ + "db": extensions.HumanitecResourceSpec{ + Scope: "shared", + }, "dns": extensions.HumanitecResourceSpec{ Scope: "shared", }, diff --git a/internal/humanitec/extensions/types.go b/internal/humanitec/extensions/types.go index 3b2cf8e..4112685 100644 --- a/internal/humanitec/extensions/types.go +++ b/internal/humanitec/extensions/types.go @@ -29,10 +29,12 @@ package extensions // dns: // scope: shared type HumanitecExtensionsSpec struct { - ApiVersion string `mapstructure:"apiVersion"` - Profile string `mapstructure:"profile"` - Spec map[string]interface{} `mapstructure:"spec"` - Resources HumanitecResourcesSpecs `mapstructure:"resources"` + ApiVersion string `mapstructure:"apiVersion"` + Profile string `mapstructure:"profile"` + Spec map[string]interface{} `mapstructure:"spec"` + + // DEPRECATED: Should use score resources annotations instead + Resources HumanitecResourcesSpecs `mapstructure:"resources"` } // HumanitecResourcesSpecs is a map of workload resources specifications. diff --git a/internal/humanitec/templates.go b/internal/humanitec/templates.go index 2a580d3..4055b1e 100644 --- a/internal/humanitec/templates.go +++ b/internal/humanitec/templates.go @@ -53,7 +53,13 @@ func buildContext(metadata score.WorkloadMeta, resources score.ResourcesSpecs, e if res.Type == "workload" { log.Println("Warning: 'workload' is a reserved resource type. Its usage may lead to compatibility issues with future releases of this application.") } - if resExt, exists := ext[resName]; exists && resExt.Scope == "shared" { + scope, hasAnnotation := res.Metadata.Annotations[ResourceScopeAnnotationLabel] + // DEPRECATED: Should use resource annotations instead + if resExt, hasMeta := ext[resName]; hasMeta && !hasAnnotation { + scope = resExt.Scope + } + // END (DEPRECATED) + if scope == "shared" { source = fmt.Sprintf("shared.%s", resName) } else { source = fmt.Sprintf("externals.%s", resName) From 0f98c262e47946ee0942519aabb88d3807b90ab1 Mon Sep 17 00:00:00 2001 From: Eugene Yarshevich Date: Tue, 18 Apr 2023 14:29:22 +0300 Subject: [PATCH 2/5] feat: added resource paramenters support Signed-off-by: Eugene Yarshevich --- internal/humanitec/convert.go | 16 ++++++++++++---- internal/humanitec/convert_test.go | 22 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/internal/humanitec/convert.go b/internal/humanitec/convert.go index 7946db1..b499df7 100644 --- a/internal/humanitec/convert.go +++ b/internal/humanitec/convert.go @@ -184,17 +184,25 @@ func ConvertSpec(name, envID string, spec *score.WorkloadSpec, ext *extensions.H switch scope { case "", "external": - externals[name] = map[string]interface{}{ + var extRes = map[string]interface{}{ "type": res.Type, } + if len(res.Params) > 0 { + extRes["params"] = res.Params + } + externals[name] = extRes case "shared": + var sharedRes = map[string]interface{}{ + "type": res.Type, + } + if len(res.Params) > 0 { + sharedRes["params"] = res.Params + } shared = append(shared, humanitec.UpdateAction{ Operation: "add", Path: "/" + name, - Value: map[string]interface{}{ - "type": res.Type, - }, + Value: sharedRes, }) default: diff --git a/internal/humanitec/convert_test.go b/internal/humanitec/convert_test.go index 1d54181..11909cf 100644 --- a/internal/humanitec/convert_test.go +++ b/internal/humanitec/convert_test.go @@ -207,6 +207,9 @@ func TestScoreConvert(t *testing.T) { Properties: map[string]score.ResourcePropertySpec{ "domain": {}, }, + Params: map[string]interface{}{ + "test": "value", + }, }, "data": { Type: "volume", @@ -225,6 +228,14 @@ func TestScoreConvert(t *testing.T) { "user_name": {Required: true, Secret: true}, "password": {Required: true, Secret: true}, }, + Params: map[string]interface{}{ + "extensions": map[string]interface{}{ + "uuid-ossp": map[string]interface{}{ + "schema": "uuid_schema", + "version": "1.1", + }, + }, + }, }, "orders": { Type: "service", @@ -317,6 +328,14 @@ func TestScoreConvert(t *testing.T) { }, "db": map[string]interface{}{ "type": "postgres", + "params": map[string]interface{}{ + "extensions": map[string]interface{}{ + "uuid-ossp": map[string]interface{}{ + "schema": "uuid_schema", + "version": "1.1", + }, + }, + }, }, }, }, @@ -328,6 +347,9 @@ func TestScoreConvert(t *testing.T) { Path: "/dns", Value: map[string]interface{}{ "type": "dns", + "params": map[string]interface{}{ + "test": "value", + }, }, }, }, From af6aebe9fe421054a65560f8a3f3d8e6d05ee80c Mon Sep 17 00:00:00 2001 From: Eugene Yarshevich Date: Wed, 19 Apr 2023 09:52:38 +0300 Subject: [PATCH 3/5] feat: override humanitec resource id with annotations Signed-off-by: Eugene Yarshevich --- internal/humanitec/convert.go | 32 +++++++++++++++++------------- internal/humanitec/convert_test.go | 8 ++++---- internal/humanitec/templates.go | 13 ++++++++---- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/internal/humanitec/convert.go b/internal/humanitec/convert.go index b499df7..e81fae4 100644 --- a/internal/humanitec/convert.go +++ b/internal/humanitec/convert.go @@ -19,7 +19,7 @@ import ( ) const ( - ResourceScopeAnnotationLabel = "humanitec.io/scope" + AnnotationLabelResourceId = "score.humanitec.io/resId" ) // getProbeDetails extracts an httpGet probe details from the source spec. @@ -171,28 +171,33 @@ func ConvertSpec(name, envID string, spec *score.WorkloadSpec, ext *extensions.H continue default: - scope, hasAnnotation := res.Metadata.Annotations[ResourceScopeAnnotationLabel] + resId, hasAnnotation := res.Metadata.Annotations[AnnotationLabelResourceId] + if resId == "" { + resId = fmt.Sprintf("externals.%s", name) + } + // DEPRECATED: Should use resource annotations instead if meta, hasMeta := ext.Resources[name]; hasMeta { log.Printf("Warning: Extensions for resources has been deprecated. Use Score resource annotations instead. Extensions are stil configured for '%s'.\n", name) - if !hasAnnotation { - scope = meta.Scope + if !hasAnnotation && (meta.Scope == "" || meta.Scope == "externals") { + resId = fmt.Sprintf("externals.%s", name) + } else if !hasAnnotation && meta.Scope == "shared" { + resId = fmt.Sprintf("shared.%s", name) } } // END (DEPRECATED) - switch scope { - - case "", "external": + if strings.HasPrefix(resId, "externals.") { + var resName = strings.Replace(resId, "externals.", "", 1) var extRes = map[string]interface{}{ "type": res.Type, } if len(res.Params) > 0 { extRes["params"] = res.Params } - externals[name] = extRes - - case "shared": + externals[resName] = extRes + } else if strings.HasPrefix(resId, "shared.") { + var resName = strings.Replace(resId, "shared.", "", 1) var sharedRes = map[string]interface{}{ "type": res.Type, } @@ -201,12 +206,11 @@ func ConvertSpec(name, envID string, spec *score.WorkloadSpec, ext *extensions.H } shared = append(shared, humanitec.UpdateAction{ Operation: "add", - Path: "/" + name, + Path: "/" + resName, Value: sharedRes, }) - - default: - log.Printf("Warning: Invalid scope value '%s' for resource '%s'. Not supported.\n", scope, name) + } else { + log.Printf("Warning: Invalid resource id value '%s'. Not supported.\n", resId) } } } diff --git a/internal/humanitec/convert_test.go b/internal/humanitec/convert_test.go index 11909cf..d101aa1 100644 --- a/internal/humanitec/convert_test.go +++ b/internal/humanitec/convert_test.go @@ -193,7 +193,7 @@ func TestScoreConvert(t *testing.T) { "env": { Metadata: score.ResourceMeta{ Annotations: map[string]string{ - ResourceScopeAnnotationLabel: "external", + AnnotationLabelResourceId: "externals.should-ignore-this-one", }, }, Type: "environment", @@ -217,7 +217,7 @@ func TestScoreConvert(t *testing.T) { "db": { Metadata: score.ResourceMeta{ Annotations: map[string]string{ - ResourceScopeAnnotationLabel: "external", + AnnotationLabelResourceId: "externals.annotations-db-id", }, }, Type: "postgres", @@ -288,7 +288,7 @@ func TestScoreConvert(t *testing.T) { "DEBUG": "${values.DEBUG}", "LOGS_LEVEL": "${pod.debug.level}", "ORDERS_SERVICE": "http://${modules.orders.service.name}:${modules.orders.service.port}/api", - "CONNECTION_STRING": "postgresql://${externals.db.host}:${externals.db.port}/${externals.db.name}", + "CONNECTION_STRING": "postgresql://${externals.annotations-db-id.host}:${externals.annotations-db-id.port}/${externals.annotations-db-id.name}", "DOMAIN_NAME": "${shared.dns.domain}", }, "files": map[string]interface{}{ @@ -326,7 +326,7 @@ func TestScoreConvert(t *testing.T) { "data": map[string]interface{}{ "type": "volume", }, - "db": map[string]interface{}{ + "annotations-db-id": map[string]interface{}{ "type": "postgres", "params": map[string]interface{}{ "extensions": map[string]interface{}{ diff --git a/internal/humanitec/templates.go b/internal/humanitec/templates.go index 4055b1e..1cb195a 100644 --- a/internal/humanitec/templates.go +++ b/internal/humanitec/templates.go @@ -53,14 +53,19 @@ func buildContext(metadata score.WorkloadMeta, resources score.ResourcesSpecs, e if res.Type == "workload" { log.Println("Warning: 'workload' is a reserved resource type. Its usage may lead to compatibility issues with future releases of this application.") } - scope, hasAnnotation := res.Metadata.Annotations[ResourceScopeAnnotationLabel] + resId, hasAnnotation := res.Metadata.Annotations[AnnotationLabelResourceId] // DEPRECATED: Should use resource annotations instead if resExt, hasMeta := ext[resName]; hasMeta && !hasAnnotation { - scope = resExt.Scope + if resExt.Scope == "" || resExt.Scope == "external" { + resId = fmt.Sprintf("externals.%s", resName) + } else if resExt.Scope == "shared" { + resId = fmt.Sprintf("shared.%s", resName) + } } // END (DEPRECATED) - if scope == "shared" { - source = fmt.Sprintf("shared.%s", resName) + + if resId != "" { + source = resId } else { source = fmt.Sprintf("externals.%s", resName) } From a4de2fc8b9c2b9bfc4d516d8b31c0448b9bd5c54 Mon Sep 17 00:00:00 2001 From: Eugene Yarshevich Date: Fri, 21 Apr 2023 11:47:30 +0300 Subject: [PATCH 4/5] fix: updated warning message for deprecated resource extensions Signed-off-by: Eugene Yarshevich --- internal/humanitec/convert.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/humanitec/convert.go b/internal/humanitec/convert.go index e81fae4..5b91df0 100644 --- a/internal/humanitec/convert.go +++ b/internal/humanitec/convert.go @@ -178,7 +178,7 @@ func ConvertSpec(name, envID string, spec *score.WorkloadSpec, ext *extensions.H // DEPRECATED: Should use resource annotations instead if meta, hasMeta := ext.Resources[name]; hasMeta { - log.Printf("Warning: Extensions for resources has been deprecated. Use Score resource annotations instead. Extensions are stil configured for '%s'.\n", name) + log.Printf("Warning: Extensions for resources has been deprecated. Use '%s' resource annotation instead. Extensions are still configured for '%s'.\n", AnnotationLabelResourceId, name) if !hasAnnotation && (meta.Scope == "" || meta.Scope == "externals") { resId = fmt.Sprintf("externals.%s", name) } else if !hasAnnotation && meta.Scope == "shared" { From 31ce21ac6e0a2ee5563dad34627d0f35a3564633 Mon Sep 17 00:00:00 2001 From: Eugene Yarshevich Date: Mon, 15 May 2023 16:22:19 +0300 Subject: [PATCH 5/5] fix: added support for fully qualified resource references via annotations Signed-off-by: Eugene Yarshevich --- internal/humanitec/convert.go | 87 +++++++++++++++++++-------- internal/humanitec/convert_test.go | 94 ++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 23 deletions(-) diff --git a/internal/humanitec/convert.go b/internal/humanitec/convert.go index 5b91df0..5378ceb 100644 --- a/internal/humanitec/convert.go +++ b/internal/humanitec/convert.go @@ -22,6 +22,44 @@ const ( AnnotationLabelResourceId = "score.humanitec.io/resId" ) +// parseResourceId extracts resource ID details from a resource reference string. +// Supported reference string formants: +// +// {resId} +// {externals|shared}.{resId} +// modules.{workloadId}.{externals|shared}.{resId} +func parseResourceId(ref string) (workload, scope, resId string, err error) { + var segments = strings.SplitN(ref, ".", 4) + switch len(segments) { + case 4: + if segments[0] != "modules" { + err = fmt.Errorf("invalid resource reference '%s': not supported", ref) + return + } + workload = segments[1] + scope = segments[2] + resId = segments[3] + case 3: + if segments[0] != "modules" { + err = fmt.Errorf("invalid resource reference '%s': not supported", ref) + return + } + case 2: + workload = "" + scope = segments[0] + resId = segments[1] + case 1: + workload = "" + scope = "" + resId = segments[0] + default: + workload = "" + scope = "" + resId = "" + } + return +} + // getProbeDetails extracts an httpGet probe details from the source spec. // Returns nil if the source spec is empty. func getProbeDetails(probe *score.ContainerProbeSpec) map[string]interface{} { @@ -187,30 +225,33 @@ func ConvertSpec(name, envID string, spec *score.WorkloadSpec, ext *extensions.H } // END (DEPRECATED) - if strings.HasPrefix(resId, "externals.") { - var resName = strings.Replace(resId, "externals.", "", 1) - var extRes = map[string]interface{}{ - "type": res.Type, - } - if len(res.Params) > 0 { - extRes["params"] = res.Params - } - externals[resName] = extRes - } else if strings.HasPrefix(resId, "shared.") { - var resName = strings.Replace(resId, "shared.", "", 1) - var sharedRes = map[string]interface{}{ - "type": res.Type, - } - if len(res.Params) > 0 { - sharedRes["params"] = res.Params + if mod, scope, resName, err := parseResourceId(resId); err != nil { + log.Printf("Warning: %v.\n", err) + } else if mod == "" || mod == spec.Metadata.Name { + if scope == "externals" { + var extRes = map[string]interface{}{ + "type": res.Type, + } + if len(res.Params) > 0 { + extRes["params"] = res.Params + } + externals[resName] = extRes + } else if scope == "shared" { + var resName = strings.Replace(resId, "shared.", "", 1) + var sharedRes = map[string]interface{}{ + "type": res.Type, + } + if len(res.Params) > 0 { + sharedRes["params"] = res.Params + } + shared = append(shared, humanitec.UpdateAction{ + Operation: "add", + Path: "/" + resName, + Value: sharedRes, + }) + } else { + log.Printf("Warning: invalid resource reference '%s': not supported.\n", resId) } - shared = append(shared, humanitec.UpdateAction{ - Operation: "add", - Path: "/" + resName, - Value: sharedRes, - }) - } else { - log.Printf("Warning: Invalid resource id value '%s'. Not supported.\n", resId) } } } diff --git a/internal/humanitec/convert_test.go b/internal/humanitec/convert_test.go index d101aa1..1e11064 100644 --- a/internal/humanitec/convert_test.go +++ b/internal/humanitec/convert_test.go @@ -8,6 +8,7 @@ The Apache Software Foundation (http://www.apache.org/). package humanitec import ( + "errors" "testing" score "github.com/score-spec/score-go/types" @@ -16,6 +17,86 @@ import ( "github.com/stretchr/testify/assert" ) +func TestParseResourceId(t *testing.T) { + var tests = []struct { + Name string + ResourceReference string + ExpectedModuleId string + ExpectedScope string + ExpectedResourceId string + ExpectedError error + }{ + // Success path + // + { + Name: "Should accept empty string", + ResourceReference: "", + ExpectedResourceId: "", + ExpectedError: nil, + }, + { + Name: "Should accept resource ID only", + ResourceReference: "test-res-id", + ExpectedResourceId: "test-res-id", + ExpectedError: nil, + }, + { + Name: "Should accept external resource reference", + ResourceReference: "externals.test-res-id", + ExpectedScope: "externals", + ExpectedResourceId: "test-res-id", + ExpectedError: nil, + }, + { + Name: "Should accept shared resource reference", + ResourceReference: "shared.test-res-id", + ExpectedScope: "shared", + ExpectedResourceId: "test-res-id", + ExpectedError: nil, + }, + { + Name: "Should accept foreighn module resource reference", + ResourceReference: "modules.test-module.externals.test-res-id", + ExpectedModuleId: "test-module", + ExpectedScope: "externals", + ExpectedResourceId: "test-res-id", + ExpectedError: nil, + }, + + // Errors handling + // + { + Name: "Should reject incomplete resource reference", + ResourceReference: "test-module.externals.test-res-id", + ExpectedError: errors.New("not supported"), + }, + { + Name: "Should reject non-module resource reference", + ResourceReference: "something.test-something.externals.test-res-id", + ExpectedError: errors.New("not supported"), + }, + } + + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + mod, scope, resId, err := parseResourceId(tt.ResourceReference) + + if tt.ExpectedError != nil { + // On Error + // + assert.ErrorContains(t, err, tt.ExpectedError.Error()) + } else { + // On Success + // + assert.NoError(t, err) + assert.Equal(t, tt.ExpectedModuleId, mod) + assert.Equal(t, tt.ExpectedScope, scope) + assert.Equal(t, tt.ExpectedResourceId, resId) + } + }) + } +} + func TestScoreConvert(t *testing.T) { const ( envID = "test" @@ -168,6 +249,7 @@ func TestScoreConvert(t *testing.T) { "ORDERS_SERVICE": "http://${resources.orders.name}:${resources.orders.port}/api", "CONNECTION_STRING": "postgresql://${resources.db.host}:${resources.db.port}/${resources.db.name}", "DOMAIN_NAME": "${resources.dns.domain}", + "EXTERNAL_RESOURCE": "${resources.external-resource.name}", }, Files: []score.FileMountSpec{ { @@ -244,6 +326,17 @@ func TestScoreConvert(t *testing.T) { "port": {}, }, }, + "external-resource": { + Metadata: score.ResourceMeta{ + Annotations: map[string]string{ + AnnotationLabelResourceId: "modules.test-module.externals.test-resource", + }, + }, + Type: "some-type", + Properties: map[string]score.ResourcePropertySpec{ + "name": {Required: false}, + }, + }, }, }, Extensions: &extensions.HumanitecExtensionsSpec{ @@ -290,6 +383,7 @@ func TestScoreConvert(t *testing.T) { "ORDERS_SERVICE": "http://${modules.orders.service.name}:${modules.orders.service.port}/api", "CONNECTION_STRING": "postgresql://${externals.annotations-db-id.host}:${externals.annotations-db-id.port}/${externals.annotations-db-id.name}", "DOMAIN_NAME": "${shared.dns.domain}", + "EXTERNAL_RESOURCE": "${modules.test-module.externals.test-resource.name}", }, "files": map[string]interface{}{ "/etc/backend/config.yaml": map[string]interface{}{