@@ -66,21 +66,32 @@ func ConvertSpec(state *project.State, spec *score.Workload) (*compose.Project,
6666
6767 // When multiple containers are specified we need to identify one container as the "main" container which will own
6868 // the network and use the native workload name. All other containers in this workload will have the container
69- // name appended as a suffix. We use the natural sort order of the container names and pick the first one
69+ // name appended as a suffix. We use the natural sort order of the container names and pick the first one that
70+ // is not expected to exit (i.e. does not have all before entries with ready: complete).
7071 containerNames := make ([]string , 0 , len (spec .Containers ))
7172 for name := range spec .Containers {
7273 containerNames = append (containerNames , name )
7374 }
7475 sort .Strings (containerNames )
7576
77+ // Determine which container should own the network namespace.
78+ // By definition (enforced by validation), there must be at least one container with no
79+ // 'before' entries — otherwise the before relationships would form a cycle.
80+ primaryContainer := containerNames [0 ]
81+ for _ , name := range containerNames {
82+ if len (spec .Containers [name ].Before ) == 0 {
83+ primaryContainer = name
84+ break
85+ }
86+ }
87+
7688 variablesSubstitutor := framework.Substituter {
7789 Replacer : deferredSubstitutionFunction ,
7890 UnEscaper : func (s string ) (string , error ) {
7991 return s , nil
8092 },
8193 }
8294
83- var firstService string
8495 for _ , containerName := range containerNames {
8596 cSpec := spec .Containers [containerName ]
8697
@@ -171,22 +182,76 @@ func ConvertSpec(state *project.State, spec *score.Workload) (*compose.Project,
171182 svc .Image = ""
172183 }
173184
174- // if we are not the "first" service, then inherit the network from the first service
175- if firstService == "" {
176- firstService = svc .Name
185+ // if we are not the primary container, then inherit the network from the primary service.
186+ // However, skip network_mode for containers that are expected to exit (all their before
187+ // entries have ready: complete) to avoid circular dependencies.
188+ if containerName == primaryContainer {
177189 // We name the containers as (workload name)-(container name) but we want the name for the main network
178190 // interface for be (workload name). So we set the hostname itself. This means that workloads cannot have
179191 // the same name within the project. But that's already enforced elsewhere.
180192 svc .Hostname = workloadName
181- } else {
193+ } else if ! isInitContainer ( spec . Containers [ containerName ]) {
182194 svc .Ports = nil
183- svc .NetworkMode = "service:" + firstService
195+ svc .NetworkMode = "service:" + workloadName + "-" + primaryContainer
184196 }
185197 composeProject .Services [svc .Name ] = svc
186198 }
199+
200+ // Invert before -> depends_on: if container A declares before: {B: {ready: complete}},
201+ // then service B depends_on A with the appropriate condition.
202+ for _ , containerName := range containerNames {
203+ cSpec := spec .Containers [containerName ]
204+ for targetContainerName , entry := range cSpec .Before {
205+ // Determine the compose condition from the ready field
206+ var condition string
207+ switch entry .Ready {
208+ case score .ContainerBeforeReadyComplete :
209+ condition = "service_completed_successfully"
210+ case score .ContainerBeforeReadyHealthy :
211+ condition = "service_healthy"
212+ case score .ContainerBeforeReadyStarted :
213+ condition = "service_started"
214+ default :
215+ return nil , fmt .Errorf ("containers.%s.before.%s: unknown ready condition %q" , containerName , targetContainerName , entry .Ready )
216+ }
217+
218+ if entry .Ready == score .ContainerBeforeReadyHealthy && cSpec .ReadinessProbe == nil && cSpec .LivenessProbe == nil {
219+ return nil , fmt .Errorf ("containers.%s.before: ready '%s' requires a readiness or liveness probe to be defined" , containerName , score .ContainerBeforeReadyHealthy )
220+ }
221+
222+ sourceServiceName := workloadName + "-" + containerName
223+ targetServiceName := workloadName + "-" + targetContainerName
224+
225+ // Add depends_on to the target service
226+ svc := composeProject .Services [targetServiceName ]
227+ if svc .DependsOn == nil {
228+ svc .DependsOn = make (compose.DependsOnConfig )
229+ }
230+ svc .DependsOn [sourceServiceName ] = compose.ServiceDependency {
231+ Condition : condition ,
232+ Required : true ,
233+ }
234+ composeProject .Services [targetServiceName ] = svc
235+ }
236+ }
237+
187238 return & composeProject , nil
188239}
189240
241+ // isInitContainer returns true if the container is expected to exit. This is determined
242+ // by checking if all its before entries specify ready: complete.
243+ func isInitContainer (c score.Container ) bool {
244+ if len (c .Before ) == 0 {
245+ return false
246+ }
247+ for _ , entry := range c .Before {
248+ if entry .Ready != score .ContainerBeforeReadyComplete {
249+ return false
250+ }
251+ }
252+ return true
253+ }
254+
190255// buildWorkloadAnnotations returns an annotation set for the workload service.
191256func buildWorkloadAnnotations (name string , spec * score.Workload ) map [string ]string {
192257 var out map [string ]string
0 commit comments