Repository: score-spec/score-compose
Pull request #47 (2024-02-01 14:22:05)

chore: upgrade to newer score-go and go 1.21
This PR upgrades this repo to the latest commit of score-go https://github.com/score-spec/score-go/commit/562be83af6a211bbcab5388381b6222fc6bccf0d which brings in the generated Go types. This ensures that the repo stays aligned with the fields supported in the official score schema file and that there is a clear source of provenance for the types (the dependency tag should be easier to match to a commit in the score-go repo).

This is functionally a no-op with no resulting changes for any of the test cases.

We also upgrade the required Go version to 1.21 (August 2023).

#### Types of changes
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [X] New chore (expected functionality to be implemented)

#### Checklist:
- [ ] My change requires a change to the documentation.
  - [ ] I have updated the documentation accordingly.
- [X] I've signed off with an email address that matches the commit author.



Patch:
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 486bebc..41f5822 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -22,7 +22,7 @@ jobs:
         name: Set up Go
         uses: actions/setup-go@v3
         with:
-          go-version: 1.19
+          go-version: 1.21
       -
         name: Run Go Tests
         run: go test ./... -cover -race
diff --git a/Dockerfile b/Dockerfile
index 3f1001b..78f55fa 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
 # Use the official Golang image to create a build artifact.
 # This is based on Debian and sets the GOPATH to /go.
 # https://hub.docker.com/_/golang
-FROM golang:1.19 as builder
+FROM golang:1.21 as builder
 
 # https://stackoverflow.com/questions/36279253/go-compiled-binary-wont-run-in-an-alpine-docker-container-on-ubuntu-host
 ENV CGO_ENABLED=0
diff --git a/go.mod b/go.mod
index a2d0fed..78aada3 100644
--- a/go.mod
+++ b/go.mod
@@ -1,12 +1,14 @@
 module github.com/score-spec/score-compose
 
-go 1.19
+go 1.21
+
+toolchain go1.21.0
 
 require (
 	github.com/compose-spec/compose-go 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-20230905115428-131acdd2f5cf
+	github.com/score-spec/score-go v1.0.2
 	github.com/spf13/cobra v1.6.0
 	github.com/stretchr/testify v1.8.0
 	github.com/tidwall/sjson v1.2.5
diff --git a/go.sum b/go.sum
index 7c4ccc3..cdbabb2 100644
--- a/go.sum
+++ b/go.sum
@@ -9,6 +9,7 @@ github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb/go.mo
 github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
 github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
 github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
 github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
@@ -17,7 +18,6 @@ github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@@ -27,12 +27,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
-github.com/score-spec/score-go v0.0.0-20230904154058-9314581aa692 h1:2MjtKoy0SIDOJ0PowkN353IiLLcLhJOCm9tYKhqlI7Y=
-github.com/score-spec/score-go v0.0.0-20230904154058-9314581aa692/go.mod h1:3l9mvrtYKzxXDQVcYkQBD3ABTPkTzWhUMYNfGlpctoo=
-github.com/score-spec/score-go v0.0.0-20230904160618-260c98499dbf h1:CALrjCOe+mGvsAD1yX8zlV6k8W/OM/1an2mWweB3wW8=
-github.com/score-spec/score-go v0.0.0-20230904160618-260c98499dbf/go.mod h1:3l9mvrtYKzxXDQVcYkQBD3ABTPkTzWhUMYNfGlpctoo=
-github.com/score-spec/score-go v0.0.0-20230905115428-131acdd2f5cf h1:0Dt+qyYoGTXPPU5Xq/KxLDtQMMA8hX+oJ8EsjFWUajQ=
-github.com/score-spec/score-go v0.0.0-20230905115428-131acdd2f5cf/go.mod h1:3l9mvrtYKzxXDQVcYkQBD3ABTPkTzWhUMYNfGlpctoo=
+github.com/score-spec/score-go v1.0.2 h1:xuzAIK9vNr1Vi4YcsZIdsmLEkfI826pMg5R+vysdf6A=
+github.com/score-spec/score-go v1.0.2/go.mod h1:nt6TOq2Ld9SiH3Fd9NF8tiJ9L7S17OE3FNgCrSet5GQ=
 github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI=
 github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -56,9 +52,12 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+v
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
+gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
diff --git a/internal/command/run.go b/internal/command/run.go
index 4ed90fe..23a32cf 100644
--- a/internal/command/run.go
+++ b/internal/command/run.go
@@ -157,7 +157,7 @@ func run(cmd *cobra.Command, args []string) error {
 
 	// Convert SCORE spec
 	//
-	var spec score.WorkloadSpec
+	var spec score.Workload
 	if err = loader.MapSpec(&spec, srcMap); err != nil {
 		return fmt.Errorf("validating workload spec: %w", err)
 	}
diff --git a/internal/compose/convert.go b/internal/compose/convert.go
index 1663fa8..6baadce 100644
--- a/internal/compose/convert.go
+++ b/internal/compose/convert.go
@@ -17,7 +17,7 @@ import (
 )
 
 // ConvertSpec converts SCORE specification into docker-compose configuration.
-func ConvertSpec(spec *score.WorkloadSpec) (*compose.Project, ExternalVariables, error) {
+func ConvertSpec(spec *score.Workload) (*compose.Project, ExternalVariables, error) {
 	ctx, err := buildContext(spec.Metadata, spec.Resources)
 	if err != nil {
 		return nil, nil, fmt.Errorf("preparing context: %w", err)
@@ -31,18 +31,14 @@ func ConvertSpec(spec *score.WorkloadSpec) (*compose.Project, ExternalVariables,
 		}
 
 		var ports []compose.ServicePortConfig
-		if len(spec.Service.Ports) > 0 {
+		if spec.Service != nil && len(spec.Service.Ports) > 0 {
 			ports = []compose.ServicePortConfig{}
 			for _, pSpec := range spec.Service.Ports {
 				var pubPort = fmt.Sprintf("%v", pSpec.Port)
-				var tgtPort = pSpec.TargetPort
-				if pSpec.TargetPort == 0 {
-					tgtPort = pSpec.Port
-				}
 				ports = append(ports, compose.ServicePortConfig{
 					Published: pubPort,
-					Target:    uint32(tgtPort),
-					Protocol:  pSpec.Protocol,
+					Target:    uint32(DerefOr(pSpec.TargetPort, pSpec.Port)),
+					Protocol:  DerefOr(pSpec.Protocol, ""),
 				})
 			}
 		}
@@ -56,14 +52,14 @@ func ConvertSpec(spec *score.WorkloadSpec) (*compose.Project, ExternalVariables,
 		if len(cSpec.Volumes) > 0 {
 			volumes = make([]compose.ServiceVolumeConfig, len(cSpec.Volumes))
 			for idx, vol := range cSpec.Volumes {
-				if vol.Path != "" {
-					return nil, nil, fmt.Errorf("can't mount named volume with sub path '%s': %w", vol.Path, errors.New("not supported"))
+				if vol.Path != nil && *vol.Path != "" {
+					return nil, nil, fmt.Errorf("can't mount named volume with sub path '%s': %w", *vol.Path, errors.New("not supported"))
 				}
 				volumes[idx] = compose.ServiceVolumeConfig{
 					Type:     "volume",
 					Source:   ctx.Substitute(vol.Source),
 					Target:   vol.Target,
-					ReadOnly: vol.ReadOnly,
+					ReadOnly: DerefOr(vol.ReadOnly, false),
 				}
 			}
 		}
diff --git a/internal/compose/ref.go b/internal/compose/ref.go
new file mode 100644
index 0000000..d5bba71
--- /dev/null
+++ b/internal/compose/ref.go
@@ -0,0 +1,12 @@
+package compose
+
+func Ref[k any](input k) *k {
+	return &input
+}
+
+func DerefOr[k any](input *k, def k) k {
+	if input == nil {
+		return def
+	}
+	return *input
+}
diff --git a/internal/compose/templates.go b/internal/compose/templates.go
index 66e1173..d5148b8 100644
--- a/internal/compose/templates.go
+++ b/internal/compose/templates.go
@@ -25,14 +25,14 @@ var (
 // templatesContext ia an utility type that provides a context for '${...}' templates substitution
 type templatesContext struct {
 	meta      map[string]interface{}
-	resources score.ResourcesSpecs
+	resources score.WorkloadResources
 
 	// env map is populated dynamically with any refenced variable used by Substitute
 	env map[string]interface{}
 }
 
 // buildContext initializes a new templatesContext instance
-func buildContext(metadata score.WorkloadMeta, resources score.ResourcesSpecs) (*templatesContext, error) {
+func buildContext(metadata score.WorkloadMetadata, resources score.WorkloadResources) (*templatesContext, error) {
 	var metadataMap = make(map[string]interface{})
 	if decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
 		TagName: "json",


Test patch:
diff --git a/internal/compose/convert_test.go b/internal/compose/convert_test.go
index 5122074..ed42753 100644
--- a/internal/compose/convert_test.go
+++ b/internal/compose/convert_test.go
@@ -23,7 +23,7 @@ func TestScoreConvert(t *testing.T) {
 
 	var tests = []struct {
 		Name    string
-		Source  *score.WorkloadSpec
+		Source  *score.Workload
 		Project *compose.Project
 		Vars    ExternalVariables
 		Error   error
@@ -32,24 +32,24 @@ func TestScoreConvert(t *testing.T) {
 		//
 		{
 			Name: "Should convert SCORE to docker-compose spec",
-			Source: &score.WorkloadSpec{
-				Metadata: score.WorkloadMeta{
+			Source: &score.Workload{
+				Metadata: score.WorkloadMetadata{
 					Name: "test",
 				},
-				Service: score.ServiceSpec{
-					Ports: score.ServicePortsSpecs{
-						"www": score.ServicePortSpec{
+				Service: &score.WorkloadService{
+					Ports: score.WorkloadServicePorts{
+						"www": score.ServicePort{
 							Port:       80,
-							TargetPort: 8080,
+							TargetPort: Ref(8080),
 						},
-						"admin": score.ServicePortSpec{
+						"admin": score.ServicePort{
 							Port:     8080,
-							Protocol: "UDP",
+							Protocol: Ref("udp"),
 						},
 					},
 				},
-				Containers: score.ContainersSpecs{
-					"backend": score.ContainerSpec{
+				Containers: score.WorkloadContainers{
+					"backend": score.Container{
 						Image: "busybox",
 						Command: []string{
 							"/bin/sh",
@@ -87,7 +87,7 @@ func TestScoreConvert(t *testing.T) {
 							{
 								Published: "8080",
 								Target:    8080,
-								Protocol:  "UDP",
+								Protocol:  "udp",
 							},
 						},
 					},
@@ -97,12 +97,12 @@ func TestScoreConvert(t *testing.T) {
 		},
 		{
 			Name: "Should convert all resources references",
-			Source: &score.WorkloadSpec{
-				Metadata: score.WorkloadMeta{
+			Source: &score.Workload{
+				Metadata: score.WorkloadMetadata{
 					Name: "test",
 				},
-				Containers: score.ContainersSpecs{
-					"backend": score.ContainerSpec{
+				Containers: score.WorkloadContainers{
+					"backend": score.Container{
 						Image: "busybox",
 						Variables: map[string]string{
 							"DEBUG":             "${resources.env.DEBUG}",
@@ -110,16 +110,16 @@ func TestScoreConvert(t *testing.T) {
 							"DOMAIN_NAME":       "${resources.some-dns.domain_name}",
 							"CONNECTION_STRING": "postgresql://${resources.app-db.host}:${resources.app-db.port}/${resources.app-db.name}",
 						},
-						Volumes: []score.VolumeMountSpec{
+						Volumes: []score.ContainerVolumesElem{
 							{
 								Source:   "${resources.data}",
 								Target:   "/mnt/data",
-								ReadOnly: true,
+								ReadOnly: Ref(true),
 							},
 						},
 					},
 				},
-				Resources: map[string]score.ResourceSpec{
+				Resources: map[string]score.Resource{
 					"env": {
 						Type: "environment",
 					},
@@ -168,24 +168,24 @@ func TestScoreConvert(t *testing.T) {
 		//
 		{
 			Name: "Should report an error for volumes with sub path (not supported)",
-			Source: &score.WorkloadSpec{
-				Metadata: score.WorkloadMeta{
+			Source: &score.Workload{
+				Metadata: score.WorkloadMetadata{
 					Name: "test",
 				},
-				Containers: score.ContainersSpecs{
-					"backend": score.ContainerSpec{
+				Containers: score.WorkloadContainers{
+					"backend": score.Container{
 						Image: "busybox",
-						Volumes: []score.VolumeMountSpec{
+						Volumes: []score.ContainerVolumesElem{
 							{
 								Source:   "${resources.data}",
-								Path:     "sub/path",
 								Target:   "/mnt/data",
-								ReadOnly: true,
+								Path:     Ref("sub/path"),
+								ReadOnly: Ref(true),
 							},
 						},
 					},
 				},
-				Resources: map[string]score.ResourceSpec{
+				Resources: map[string]score.Resource{
 					"data": {
 						Type: "volume",
 					},
diff --git a/internal/compose/templates_test.go b/internal/compose/templates_test.go
index 03a0caf..32fc4d4 100644
--- a/internal/compose/templates_test.go
+++ b/internal/compose/templates_test.go
@@ -15,15 +15,15 @@ import (
 )
 
 func TestMapVar(t *testing.T) {
-	var meta = score.WorkloadMeta{
+	var meta = score.WorkloadMetadata{
 		Name: "test-name",
 	}
 
-	var resources = score.ResourcesSpecs{
-		"env": score.ResourceSpec{
+	var resources = score.WorkloadResources{
+		"env": score.Resource{
 			Type: "environment",
 		},
-		"db": score.ResourceSpec{
+		"db": score.Resource{
 			Type: "postgres",
 		},
 	}
@@ -51,15 +51,15 @@ func TestMapVar(t *testing.T) {
 }
 
 func TestSubstitute(t *testing.T) {
-	var meta = score.WorkloadMeta{
+	var meta = score.WorkloadMetadata{
 		Name: "test-name",
 	}
 
-	var resources = score.ResourcesSpecs{
-		"env": score.ResourceSpec{
+	var resources = score.WorkloadResources{
+		"env": score.Resource{
 			Type: "environment",
 		},
-		"db": score.ResourceSpec{
+		"db": score.Resource{
 			Type: "postgres",
 		},
 	}