Repository: score-spec/score-compose
Pull request #121 (2024-04-22 21:17:09)

fix: set workload network hostname to workload name
@mathieu-benoit and I were trying to get a Score adaption of https://github.com/Azure-Samples/aks-store-demo/tree/main working with score-compose but found that a routing configuration assumed that the hostname was equal to the workload name. However score compose was using (workload name)-(1st container name) because it shares the network of the 1st container.

This PR fixes this by setting the hostname directly on the 1st container as appropriate. 

This shouldn't break compatibility and should make things more readable when workloads only contain one container.




Patch:
diff --git a/examples/01-hello/README.md b/examples/01-hello/README.md
index b71b013..1504ede 100644
--- a/examples/01-hello/README.md
+++ b/examples/01-hello/README.md
@@ -24,8 +24,7 @@ $ score-compose generate score.yaml
 
 The `init` will create the `.score-compose` directory. The `generate` command will add the input `score.yaml` workload to the `.score-compose/state.yaml` state file and regenerate the output `compose.yaml`.
 
-```console
-$ cat compose.yaml
+```yaml
 name: 01-hello
 services:
     hello-world-hello:
@@ -34,6 +33,7 @@ services:
             - while true; do echo Hello World!; sleep 5; done
         entrypoint:
             - /bin/sh
+        hostname: hello-world
         image: busybox
 ```
 
diff --git a/examples/02-environment/README.md b/examples/02-environment/README.md
index 85064c5..7a8ced2 100644
--- a/examples/02-environment/README.md
+++ b/examples/02-environment/README.md
@@ -36,7 +36,7 @@ $ score-compose generate score.yaml
 
 And it returns
 
-```console
+```yaml
 name: 02-environment
 services:
     hello-world-hello:
@@ -49,6 +49,7 @@ services:
             GREETING: Hello
             NAME: ${NAME}
             WORKLOAD_NAME: hello-world
+        hostname: hello-world
         image: busybox
 ```
 
diff --git a/examples/04-multiple-workloads/README.md b/examples/04-multiple-workloads/README.md
index b303568..b644613 100644
--- a/examples/04-multiple-workloads/README.md
+++ b/examples/04-multiple-workloads/README.md
@@ -53,10 +53,12 @@ services:
     hello-world-2-first:
         environment:
             NGINX_PORT: "8080"
+        hostname: hello-world-2
         image: nginx:latest
     hello-world-first:
         environment:
             NGINX_PORT: "8080"
+        hostname: hello-world
         image: nginx:latest
     hello-world-second:
         environment:
diff --git a/examples/08-service-port-resource/README.md b/examples/08-service-port-resource/README.md
index dab66c0..c7f9ab0 100644
--- a/examples/08-service-port-resource/README.md
+++ b/examples/08-service-port-resource/README.md
@@ -51,6 +51,7 @@ When we run `score-compose init; score-compose generate score*.yaml`, the result
 name: temptest
 services:
     workload-a-example:
+        hostname: workload-a
         image: nginx
     workload-b-example:
         command:
@@ -60,6 +61,7 @@ services:
             - /bin/sh
         environment:
             DEPENDENCY_URL: http://workload-a-example:80
+        hostname: workload-b
         image: busybox
 ```
 
diff --git a/internal/compose/convert.go b/internal/compose/convert.go
index 333ba48..d04c062 100644
--- a/internal/compose/convert.go
+++ b/internal/compose/convert.go
@@ -164,6 +164,10 @@ func ConvertSpec(state *project.State, spec *score.Workload) (*compose.Project,
 		// if we are not the "first" service, then inherit the network from the first service
 		if firstService == "" {
 			firstService = svc.Name
+			// We name the containers as (workload name)-(container name) but we want the name for the main network
+			// interface for be (workload name). So we set the hostname itself. This means that workloads cannot have
+			// the same name within the project. But that's already enforced elsewhere.
+			svc.Hostname = workloadName
 		} else {
 			svc.Ports = nil
 			svc.NetworkMode = "service:" + firstService
diff --git a/internal/provisioners/core.go b/internal/provisioners/core.go
index e2dac14..450682a 100644
--- a/internal/provisioners/core.go
+++ b/internal/provisioners/core.go
@@ -24,7 +24,6 @@ import (
 	"path/filepath"
 	"slices"
 	"strconv"
-	"strings"
 
 	compose "github.com/compose-spec/compose-go/v2/types"
 	"github.com/score-spec/score-go/framework"
@@ -240,16 +239,10 @@ func (po *ProvisionOutput) ApplyToStateAndProject(state *project.State, resUid f
 func buildWorkloadServices(state *project.State) map[string]NetworkService {
 	out := make(map[string]NetworkService, len(state.Workloads))
 	for workloadName, workloadState := range state.Workloads {
-		// the hostname of a workload is the <workload name>-<first container name>
-		var firstContainerName string
-		for name := range workloadState.Spec.Containers {
-			if firstContainerName == "" || strings.Compare(name, firstContainerName) < 0 {
-				firstContainerName = name
-			}
-		}
 		// setup ports exposure
 		ns := NetworkService{
-			ServiceName: workloadName + "-" + firstContainerName,
+			// the hostname of a workload is the <workload name>
+			ServiceName: workloadName,
 			Ports:       make(map[string]ServicePort),
 		}
 		if workloadState.Spec.Service != nil {


Test patch:
diff --git a/internal/command/generate_examples_test.go b/internal/command/generate_examples_test.go
index 512b3e4..6f64013 100644
--- a/internal/command/generate_examples_test.go
+++ b/internal/command/generate_examples_test.go
@@ -46,6 +46,7 @@ services:
             - while true; do echo Hello World!; sleep 5; done
         entrypoint:
             - /bin/sh
+        hostname: hello-world
         image: busybox
 `,
 		},
@@ -64,6 +65,7 @@ services:
             GREETING: Hello
             NAME: ${NAME}
             WORKLOAD_NAME: hello-world
+        hostname: hello-world
         image: busybox
 `,
 		},
@@ -78,6 +80,7 @@ services:
             - while true; do cat /fileA.txt; cat /fileB.txt; sleep 5; done
         entrypoint:
             - /bin/sh
+        hostname: hello-world
         image: busybox
         volumes:
             - type: bind
@@ -99,10 +102,12 @@ services:
     hello-world-2-first:
         environment:
             NGINX_PORT: "8080"
+        hostname: hello-world-2
         image: nginx:latest
     hello-world-first:
         environment:
             NGINX_PORT: "8080"
+        hostname: hello-world
         image: nginx:latest
     hello-world-second:
         environment:
@@ -117,6 +122,7 @@ services:
 			expected: `name: 05-volume-mounts
 services:
     hello-world-first:
+        hostname: hello-world
         image: nginx:latest
         volumes:
             - type: volume
@@ -142,6 +148,7 @@ services:
     hello-world-web:
         environment:
             DEBUG: "true"
+        hostname: hello-world
         image: nginx
 `,
 		},
@@ -151,6 +158,7 @@ services:
 			expected: `name: 08-service-port-resource
 services:
     workload-a-example:
+        hostname: workload-a
         image: nginx
     workload-b-example:
         command:
@@ -159,7 +167,8 @@ services:
         entrypoint:
             - /bin/sh
         environment:
-            DEPENDENCY_URL: http://workload-a-example:80
+            DEPENDENCY_URL: http://workload-a:80
+        hostname: workload-b
         image: busybox
 `,
 		},
diff --git a/internal/command/generate_test.go b/internal/command/generate_test.go
index f99ef03..6344acc 100644
--- a/internal/command/generate_test.go
+++ b/internal/command/generate_test.go
@@ -155,6 +155,7 @@ services:
         environment:
             EXAMPLE_VARIABLE: example-value
             THING: ${THING}
+        hostname: example
         image: nginx:latest
 `
 	assert.Equal(t, expectedOutput, string(raw))
@@ -210,6 +211,7 @@ containers:
 		expectedOutput := `name: "001"
 services:
     example-example:
+        hostname: example
         image: busybox:latest
 `
 		assert.Equal(t, expectedOutput, string(raw))
@@ -236,6 +238,7 @@ services:
     example-example:
         build:
             context: ./dir
+        hostname: example
 `
 		assert.Equal(t, expectedOutput, string(raw))
 		// generate again just for luck
@@ -262,6 +265,7 @@ services:
             context: ./dir
             args:
                 DEBUG: "true"
+        hostname: example
 `
 		assert.Equal(t, expectedOutput, string(raw))
 	})
@@ -293,6 +297,7 @@ containers:
 	assert.Equal(t, `name: "001"
 services:
     example-example:
+        hostname: example
         image: foo
         volumes:
             - type: bind
@@ -505,6 +510,7 @@ services:
             wait-for-resources:
                 condition: service_started
                 required: false
+        hostname: example
         image: foo
     generic_service:
         image: other
@@ -603,6 +609,7 @@ services:
             wait-for-resources:
                 condition: service_started
                 required: false
+        hostname: example
         image: busybox
     foo-service:
         image: foo-image
@@ -720,7 +727,7 @@ resources:
 		assert.Len(t, sd.State.Workloads, 1)
 		assert.Len(t, sd.State.Resources, 1)
 		assert.Equal(t, map[string]interface{}{
-			"hostname": "example-example",
+			"hostname": "example",
 			"port":     80,
 		}, sd.State.Resources["workload-port.default#example.first"].State)
 	})
@@ -756,6 +763,7 @@ services:
     example-example:
         environment:
             REF: thing
+        hostname: example
         image: foo
 `, string(raw))
 
@@ -832,11 +840,11 @@ resources:
 		"default-provisioners-routing-instance": map[string]interface{}{
 			"hosts": map[string]interface{}{
 				"localhost1": map[string]interface{}{
-					"route.default#example.r1": map[string]interface{}{"path": "/first", "port": 8080, "target": "example-example:8080", "path_type": "Prefix"},
-					"route.default#example.r2": map[string]interface{}{"path": "/second", "port": 8080, "target": "example-example:8080", "path_type": "Prefix"},
+					"route.default#example.r1": map[string]interface{}{"path": "/first", "port": 8080, "target": "example:8080", "path_type": "Prefix"},
+					"route.default#example.r2": map[string]interface{}{"path": "/second", "port": 8080, "target": "example:8080", "path_type": "Prefix"},
 				},
 				"localhost2": map[string]interface{}{
-					"route.default#example.r3": map[string]interface{}{"path": "/third", "port": 8080, "target": "example-example:8080", "path_type": "Exact"},
+					"route.default#example.r3": map[string]interface{}{"path": "/third", "port": 8080, "target": "example:8080", "path_type": "Exact"},
 				},
 			},
 			"instancePort": 8080,
@@ -895,6 +903,7 @@ services:
     example-example:
         environment:
             ONE: ${UNKNOWN_SCORE_VARIABLE}
+        hostname: example
         image: foo
 `, string(raw))
 }
diff --git a/internal/command/init_test.go b/internal/command/init_test.go
index 6ea28b0..cceb152 100644
--- a/internal/command/init_test.go
+++ b/internal/command/init_test.go
@@ -92,6 +92,7 @@ func TestInitNominal(t *testing.T) {
   example-hello-world:
     environment:
       EXAMPLE_VARIABLE: example-value
+    hostname: example
     image: nginx:latest
     ports:
       - target: 80
diff --git a/internal/command/run_test.go b/internal/command/run_test.go
index 2c25ae3..e517746 100644
--- a/internal/command/run_test.go
+++ b/internal/command/run_test.go
@@ -186,6 +186,7 @@ resources:
 	assert.Equal(t, map[string]interface{}{
 		"services": map[string]interface{}{
 			"example-workload-name123-container-one1": map[string]interface{}{
+				"hostname":   "example-workload-name123",
 				"image":      "localhost:4000/repo/my-image:tag",
 				"entrypoint": []interface{}{"/bin/sh", "-c"},
 				"command":    []interface{}{"hello", "world"},
@@ -311,6 +312,7 @@ func TestRunExample01(t *testing.T) {
       - while true; do echo Hello World!; sleep 5; done
     entrypoint:
       - /bin/sh
+    hostname: hello-world
     image: busybox
 `
 
@@ -358,6 +360,7 @@ containers:
   hello-world-hello:
     build:
       context: ./test
+    hostname: hello-world
 `
 
 	assert.Equal(t, expectedOutput, stdout)
@@ -392,6 +395,7 @@ containers:
 
 	expectedOutput := `services:
   hello-world-hello:
+    hostname: hello-world
     image: nginx
 `
 
@@ -421,6 +425,7 @@ containers:
 
 	expectedOutput := `services:
   hello-world-hello:
+    hostname: hello-world
     image: bananas:latest
 `
 
diff --git a/internal/compose/convert_test.go b/internal/compose/convert_test.go
index d33ebcb..bb5097e 100644
--- a/internal/compose/convert_test.go
+++ b/internal/compose/convert_test.go
@@ -82,8 +82,9 @@ func TestScoreConvert(t *testing.T) {
 			Project: &compose.Project{
 				Services: compose.Services{
 					"test-backend": {
-						Name:  "test-backend",
-						Image: "busybox",
+						Name:     "test-backend",
+						Hostname: "test",
+						Image:    "busybox",
 						Entrypoint: compose.ShellCommand{
 							"/bin/sh",
 						},
@@ -140,8 +141,9 @@ func TestScoreConvert(t *testing.T) {
 			Project: &compose.Project{
 				Services: compose.Services{
 					"test-backend": {
-						Name:  "test-backend",
-						Image: "busybox",
+						Name:     "test-backend",
+						Hostname: "test",
+						Image:    "busybox",
 						Environment: compose.MappingWithEquals{
 							"DEBUG":             stringPtr("${DEBUG}"),
 							"LOGS_LEVEL":        stringPtr("${LOGS_LEVEL}"),
@@ -197,8 +199,9 @@ func TestScoreConvert(t *testing.T) {
 			Project: &compose.Project{
 				Services: compose.Services{
 					"test-backend": {
-						Name:  "test-backend",
-						Image: "busybox",
+						Name:     "test-backend",
+						Hostname: "test",
+						Image:    "busybox",
 						Environment: compose.MappingWithEquals{
 							"PORT": stringPtr("81"),
 						},
diff --git a/internal/provisioners/core_test.go b/internal/provisioners/core_test.go
index edfa030..c02daba 100644
--- a/internal/provisioners/core_test.go
+++ b/internal/provisioners/core_test.go
@@ -139,7 +139,7 @@ func TestProvisionResourcesWithNetworkService(t *testing.T) {
 			assert.Equal(t, "w1", input.SourceWorkload)
 			assert.Equal(t, map[string]NetworkService{
 				"w1": {
-					ServiceName: "w1-container-a",
+					ServiceName: "w1",
 					Ports: map[string]ServicePort{
 						"web":  {Name: "web", Port: 80, TargetPort: 80, Protocol: score.ServicePortProtocolTCP},
 						"80":   {Name: "web", Port: 80, TargetPort: 80, Protocol: score.ServicePortProtocolTCP},