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{}{