Repository: score-spec/score-compose
Pull request #134 (2024-05-14 09:39:49)

feat: pass through workload metadata.annotations onto services
This allows annotations to be used during post processing, and this also follows the general behavior of other score implementations.

For example injecting dapr sidecars:

```
yq '(.services) += (.services | 
  with_entries(select((.value | has("hostname")) and (.value.annotations | has("dapr.io/enabled")))) | 
  with_entries(. as $e | .key |= . + "-sidecar"| .value |= {"image": "daprio/daprd:latest", "command": ["./daprd","--app-id", ($e.value.annotations."dapr.io/app-id" | "unknown")], "network_mode": "service:" + $e.key}))' examples/01-hello/compose.yaml
```

This would support score workloads with the dapr enabled annotation.

```
apiVersion: score.dev/v1b1
metadata:
  name: foo
  annotations:
    dapr.io/enabled: "true"
    dapr.io/app-id: "foo-app"
...
```

🎸 


Patch:
diff --git a/examples/01-hello/README.md b/examples/01-hello/README.md
index 1504ede..e3c2e32 100644
--- a/examples/01-hello/README.md
+++ b/examples/01-hello/README.md
@@ -7,6 +7,8 @@ apiVersion: score.dev/v1b1
 
 metadata:
   name: hello-world
+  annotations:
+    your.custom/annotation: value
 
 containers:
   hello:
@@ -28,6 +30,9 @@ The `init` will create the `.score-compose` directory. The `generate` command wi
 name: 01-hello
 services:
     hello-world-hello:
+        annotations:
+            compose.score.dev/workload-name: hello-world
+            your.custom/annotation: value
         command:
             - -c
             - while true; do echo Hello World!; sleep 5; done
@@ -37,6 +42,8 @@ services:
         image: busybox
 ```
 
+See how the Score workload has been converted into a Compose service. With the container, command, image, and annotations. 
+
 This `compose.yaml` can then be run directly, and you can watch the output of the logs.
 
 ```console
diff --git a/examples/01-hello/score.yaml b/examples/01-hello/score.yaml
index 79485ca..8c6fc9c 100644
--- a/examples/01-hello/score.yaml
+++ b/examples/01-hello/score.yaml
@@ -2,6 +2,8 @@ apiVersion: score.dev/v1b1
 
 metadata:
   name: hello-world
+  annotations:
+    your.custom/annotation: value
 
 containers:
   hello:
diff --git a/examples/02-environment/README.md b/examples/02-environment/README.md
index 7a8ced2..2c171ad 100644
--- a/examples/02-environment/README.md
+++ b/examples/02-environment/README.md
@@ -40,6 +40,8 @@ And it returns
 name: 02-environment
 services:
     hello-world-hello:
+        annotations:
+            compose.score.dev/workload-name: hello-world
         command:
             - -c
             - while true; do echo $${GREETING} $${NAME}!; sleep 5; done
diff --git a/examples/04-multiple-workloads/README.md b/examples/04-multiple-workloads/README.md
index b644613..7887520 100644
--- a/examples/04-multiple-workloads/README.md
+++ b/examples/04-multiple-workloads/README.md
@@ -51,16 +51,22 @@ The output compose file appears as below. Notice that all 3 containers are prese
 name: 04-multiple-workloads
 services:
     hello-world-2-first:
+      annotations:
+        compose.score.dev/workload-name: hello-world-2
         environment:
             NGINX_PORT: "8080"
         hostname: hello-world-2
         image: nginx:latest
     hello-world-first:
+      annotations:
+        compose.score.dev/workload-name: hello-world
         environment:
             NGINX_PORT: "8080"
         hostname: hello-world
         image: nginx:latest
     hello-world-second:
+      annotations:
+        compose.score.dev/workload-name: hello-world
         environment:
             NGINX_PORT: "8081"
         image: nginx:latest
diff --git a/examples/08-service-port-resource/README.md b/examples/08-service-port-resource/README.md
index c7f9ab0..5e58b78 100644
--- a/examples/08-service-port-resource/README.md
+++ b/examples/08-service-port-resource/README.md
@@ -51,9 +51,13 @@ When we run `score-compose init; score-compose generate score*.yaml`, the result
 name: temptest
 services:
     workload-a-example:
+        annotations:
+            compose.score.dev/workload-name: workload-a
         hostname: workload-a
         image: nginx
     workload-b-example:
+        annotations:
+            compose.score.dev/workload-name: workload-b
         command:
             - -c
             - while true; do wget $${DEPENDENCY_URL} || true; sleep 5; done
diff --git a/internal/compose/convert.go b/internal/compose/convert.go
index 7a69a8a..d02bc44 100644
--- a/internal/compose/convert.go
+++ b/internal/compose/convert.go
@@ -141,6 +141,7 @@ func ConvertSpec(state *project.State, spec *score.Workload) (*compose.Project,
 
 		var svc = compose.ServiceConfig{
 			Name:        workloadName + "-" + containerName,
+			Annotations: buildWorkloadAnnotations(workloadName, spec),
 			Image:       cSpec.Image,
 			Entrypoint:  cSpec.Command,
 			Command:     cSpec.Args,
@@ -170,6 +171,22 @@ func ConvertSpec(state *project.State, spec *score.Workload) (*compose.Project,
 	return &composeProject, nil
 }
 
+// buildWorkloadAnnotations returns an annotation set for the workload service.
+func buildWorkloadAnnotations(name string, spec *score.Workload) map[string]string {
+	var out map[string]string
+	if a, ok := spec.Metadata["annotations"].(map[string]interface{}); ok {
+		out = make(map[string]string, len(a))
+		for k, v := range a {
+			// type is validated by the spec
+			out[k] = v.(string)
+		}
+	} else {
+		out = make(map[string]string, 1)
+	}
+	out["compose.score.dev/workload-name"] = name
+	return out
+}
+
 // convertFilesIntoVolumes converts the lists of files into a list of bind mounts in the mounts directory.
 func convertFilesIntoVolumes(state *project.State, workloadName string, containerName string, substitutionFunction func(string) (string, error)) ([]compose.ServiceVolumeConfig, error) {
 	input := state.Workloads[workloadName].Spec.Containers[containerName].Files


Test patch:
diff --git a/internal/command/generate_examples_test.go b/internal/command/generate_examples_test.go
index c43833b..11d6c38 100644
--- a/internal/command/generate_examples_test.go
+++ b/internal/command/generate_examples_test.go
@@ -41,6 +41,9 @@ func TestExample(t *testing.T) {
 			expected: `name: 01-hello
 services:
     hello-world-hello:
+        annotations:
+            compose.score.dev/workload-name: hello-world
+            your.custom/annotation: value
         command:
             - -c
             - while true; do echo Hello World!; sleep 5; done
@@ -56,6 +59,8 @@ services:
 			expected: `name: 02-environment
 services:
     hello-world-hello:
+        annotations:
+            compose.score.dev/workload-name: hello-world
         command:
             - -c
             - while true; do echo $${GREETING} $${NAME}!; sleep 5; done
@@ -75,6 +80,8 @@ services:
 			expected: `name: 03-files
 services:
     hello-world-hello:
+        annotations:
+            compose.score.dev/workload-name: hello-world
         command:
             - -c
             - while true; do cat /fileA.txt; cat /fileB.txt; sleep 5; done
@@ -100,16 +107,22 @@ services:
 			expected: `name: 04-multiple-workloads
 services:
     hello-world-2-first:
+        annotations:
+            compose.score.dev/workload-name: hello-world-2
         environment:
             NGINX_PORT: "8080"
         hostname: hello-world-2
         image: nginx:latest
     hello-world-first:
+        annotations:
+            compose.score.dev/workload-name: hello-world
         environment:
             NGINX_PORT: "8080"
         hostname: hello-world
         image: nginx:latest
     hello-world-second:
+        annotations:
+            compose.score.dev/workload-name: hello-world
         environment:
             NGINX_PORT: "8081"
         image: nginx:latest
@@ -122,6 +135,8 @@ services:
 			expected: `name: 05-volume-mounts
 services:
     hello-world-first:
+        annotations:
+            compose.score.dev/workload-name: hello-world
         hostname: hello-world
         image: nginx:latest
         volumes:
@@ -146,6 +161,8 @@ volumes:
 			expected: `name: 07-overrides
 services:
     hello-world-web:
+        annotations:
+            compose.score.dev/workload-name: hello-world
         environment:
             DEBUG: "true"
         hostname: hello-world
@@ -158,9 +175,13 @@ services:
 			expected: `name: 08-service-port-resource
 services:
     workload-a-example:
+        annotations:
+            compose.score.dev/workload-name: workload-a
         hostname: workload-a
         image: nginx
     workload-b-example:
+        annotations:
+            compose.score.dev/workload-name: workload-b
         command:
             - -c
             - while true; do wget $${DEPENDENCY_URL} || true; sleep 5; done
diff --git a/internal/command/generate_test.go b/internal/command/generate_test.go
index a9ba907..2a5c86f 100644
--- a/internal/command/generate_test.go
+++ b/internal/command/generate_test.go
@@ -152,6 +152,8 @@ func TestInitAndGenerate_with_sample(t *testing.T) {
 	expectedOutput := `name: "001"
 services:
     example-hello-world:
+        annotations:
+            compose.score.dev/workload-name: example
         environment:
             EXAMPLE_VARIABLE: example-value
             THING: ${THING}
@@ -211,6 +213,8 @@ containers:
 		expectedOutput := `name: "001"
 services:
     example-example:
+        annotations:
+            compose.score.dev/workload-name: example
         hostname: example
         image: busybox:latest
 `
@@ -236,6 +240,8 @@ services:
 		expectedOutput := `name: "001"
 services:
     example-example:
+        annotations:
+            compose.score.dev/workload-name: example
         build:
             context: ./dir
         hostname: example
@@ -261,6 +267,8 @@ services:
 		expectedOutput := `name: "001"
 services:
     example-example:
+        annotations:
+            compose.score.dev/workload-name: example
         build:
             context: ./dir
             args:
@@ -281,6 +289,8 @@ services:
 		expectedOutput := `name: "001"
 services:
     example-example:
+        annotations:
+            compose.score.dev/workload-name: example
         build:
             context: ./dir
             args:
@@ -301,6 +311,8 @@ services:
 		expectedOutput := `name: "001"
 services:
     example-example:
+        annotations:
+            compose.score.dev/workload-name: example
         build:
             context: ./dir
         hostname: example
@@ -335,6 +347,8 @@ containers:
 	assert.Equal(t, `name: "001"
 services:
     example-example:
+        annotations:
+            compose.score.dev/workload-name: example
         hostname: example
         image: foo
         volumes:
@@ -544,6 +558,8 @@ resources:
 	assert.Equal(t, `name: "001"
 services:
     example-example:
+        annotations:
+            compose.score.dev/workload-name: example
         depends_on:
             wait-for-resources:
                 condition: service_started
@@ -643,6 +659,8 @@ services:
     bar-service:
         image: value
     example-example:
+        annotations:
+            compose.score.dev/workload-name: example
         depends_on:
             wait-for-resources:
                 condition: service_started
@@ -799,6 +817,9 @@ containers:
 	assert.Equal(t, `name: "001"
 services:
     example-example:
+        annotations:
+            compose.score.dev/workload-name: example
+            key.com/foo-bar: thing
         environment:
             REF: thing
         hostname: example
@@ -939,6 +960,8 @@ resources:
 	assert.Equal(t, `name: "001"
 services:
     example-example:
+        annotations:
+            compose.score.dev/workload-name: example
         environment:
             ONE: ${UNKNOWN_SCORE_VARIABLE}
         hostname: example
@@ -1051,6 +1074,8 @@ resources:
 	assert.Equal(t, `name: "001"
 services:
     example-example:
+        annotations:
+            compose.score.dev/workload-name: example
         hostname: example
         image: busybox
         volumes:
diff --git a/internal/command/init_test.go b/internal/command/init_test.go
index 113bbb8..fc376cc 100644
--- a/internal/command/init_test.go
+++ b/internal/command/init_test.go
@@ -90,6 +90,8 @@ func TestInitNominal(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, `services:
   example-hello-world:
+    annotations:
+      compose.score.dev/workload-name: example
     environment:
       EXAMPLE_VARIABLE: example-value
     hostname: example
diff --git a/internal/command/run_test.go b/internal/command/run_test.go
index e517746..f72b920 100644
--- a/internal/command/run_test.go
+++ b/internal/command/run_test.go
@@ -186,6 +186,9 @@ resources:
 	assert.Equal(t, map[string]interface{}{
 		"services": map[string]interface{}{
 			"example-workload-name123-container-one1": map[string]interface{}{
+				"annotations": map[string]interface{}{
+					"compose.score.dev/workload-name": "example-workload-name123",
+				},
 				"hostname":   "example-workload-name123",
 				"image":      "localhost:4000/repo/my-image:tag",
 				"entrypoint": []interface{}{"/bin/sh", "-c"},
@@ -203,6 +206,9 @@ resources:
 				},
 			},
 			"example-workload-name123-container-two2": map[string]interface{}{
+				"annotations": map[string]interface{}{
+					"compose.score.dev/workload-name": "example-workload-name123",
+				},
 				"image":        "localhost:4000/repo/my-image:tag",
 				"network_mode": "service:example-workload-name123-container-one1",
 			},
@@ -307,6 +313,9 @@ func TestRunExample01(t *testing.T) {
 
 	expectedOutput := `services:
   hello-world-hello:
+    annotations:
+      compose.score.dev/workload-name: hello-world
+      your.custom/annotation: value
     command:
       - -c
       - while true; do echo Hello World!; sleep 5; done
@@ -358,6 +367,8 @@ containers:
 
 	expectedOutput := `services:
   hello-world-hello:
+    annotations:
+      compose.score.dev/workload-name: hello-world
     build:
       context: ./test
     hostname: hello-world
@@ -395,6 +406,8 @@ containers:
 
 	expectedOutput := `services:
   hello-world-hello:
+    annotations:
+      compose.score.dev/workload-name: hello-world
     hostname: hello-world
     image: nginx
 `
@@ -425,6 +438,8 @@ containers:
 
 	expectedOutput := `services:
   hello-world-hello:
+    annotations:
+      compose.score.dev/workload-name: hello-world
     hostname: hello-world
     image: bananas:latest
 `
diff --git a/internal/compose/convert_test.go b/internal/compose/convert_test.go
index cfe8bf4..37f9ce3 100644
--- a/internal/compose/convert_test.go
+++ b/internal/compose/convert_test.go
@@ -50,6 +50,9 @@ func TestScoreConvert(t *testing.T) {
 			Source: &score.Workload{
 				Metadata: score.WorkloadMetadata{
 					"name": "test",
+					"annotations": map[string]interface{}{
+						"extra.name/annotation": "foo",
+					},
 				},
 				Service: &score.WorkloadService{
 					Ports: score.WorkloadServicePorts{
@@ -82,7 +85,11 @@ func TestScoreConvert(t *testing.T) {
 			Project: &compose.Project{
 				Services: compose.Services{
 					"test-backend": {
-						Name:     "test-backend",
+						Name: "test-backend",
+						Annotations: map[string]string{
+							"compose.score.dev/workload-name": "test",
+							"extra.name/annotation":           "foo",
+						},
 						Hostname: "test",
 						Image:    "busybox",
 						Entrypoint: compose.ShellCommand{
@@ -141,7 +148,10 @@ func TestScoreConvert(t *testing.T) {
 			Project: &compose.Project{
 				Services: compose.Services{
 					"test-backend": {
-						Name:     "test-backend",
+						Name: "test-backend",
+						Annotations: map[string]string{
+							"compose.score.dev/workload-name": "test",
+						},
 						Hostname: "test",
 						Image:    "busybox",
 						Environment: compose.MappingWithEquals{
@@ -211,7 +221,10 @@ func TestScoreConvert(t *testing.T) {
 			Project: &compose.Project{
 				Services: compose.Services{
 					"test-backend": {
-						Name:     "test-backend",
+						Name: "test-backend",
+						Annotations: map[string]string{
+							"compose.score.dev/workload-name": "test",
+						},
 						Hostname: "test",
 						Image:    "busybox",
 						Environment: compose.MappingWithEquals{
@@ -269,6 +282,9 @@ func TestScoreConvert(t *testing.T) {
 			Project: &compose.Project{
 				Services: compose.Services{
 					"test-backend": {
+						Annotations: map[string]string{
+							"compose.score.dev/workload-name": "test",
+						},
 						Name:     "test-backend",
 						Hostname: "test",
 						Image:    "busybox",
@@ -277,6 +293,9 @@ func TestScoreConvert(t *testing.T) {
 						},
 					},
 					"test-frontend": {
+						Annotations: map[string]string{
+							"compose.score.dev/workload-name": "test",
+						},
 						Name:  "test-frontend",
 						Image: "busybox",
 						Environment: compose.MappingWithEquals{
@@ -313,7 +332,10 @@ func TestScoreConvert(t *testing.T) {
 			Project: &compose.Project{
 				Services: compose.Services{
 					"test-backend": {
-						Name:     "test-backend",
+						Name: "test-backend",
+						Annotations: map[string]string{
+							"compose.score.dev/workload-name": "test",
+						},
 						Image:    "busybox",
 						Hostname: "test",
 						Entrypoint: compose.ShellCommand{