github.com/jonsyu1/godel@v0.0.0-20171017211503-64567a0cf169/apps/distgo/cmd/docker/order_specs.go (about) 1 // Copyright 2016 Palantir Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package docker 16 17 import ( 18 "sort" 19 20 "github.com/pkg/errors" 21 22 "github.com/palantir/godel/apps/distgo/params" 23 ) 24 25 // OrderBuildSpecs orders the provided build specs topologically based on the dependencies among the product specs. 26 func OrderBuildSpecs(specsWithDeps []params.ProductBuildSpecWithDeps) ([]params.ProductBuildSpecWithDeps, error) { 27 var schedule []params.ProductBuildSpecWithDeps 28 graph := make(map[string]map[string]struct{}) 29 specMap := make(map[string][]params.ProductBuildSpecWithDeps) 30 // create a graph of dependencies 31 for _, curSpec := range specsWithDeps { 32 product := curSpec.Spec.ProductName 33 specMap[product] = append(specMap[product], curSpec) 34 if graph[product] == nil { 35 graph[product] = make(map[string]struct{}) 36 } 37 for _, curImage := range curSpec.Spec.DockerImages { 38 for depProduct, depTypes := range dockerDepsToMap(curImage.Deps) { 39 if !hasDockerDep(depTypes) { 40 // only add edge if its a docker image dependency 41 continue 42 } 43 if graph[depProduct] == nil { 44 graph[depProduct] = make(map[string]struct{}) 45 } 46 graph[depProduct][product] = struct{}{} 47 } 48 } 49 } 50 51 // get the topological ordering among the products 52 order, err := topologicalOrdering(graph) 53 if err != nil { 54 return nil, errors.Wrapf(err, "Failed to generate ordering among the products. The dist dependencies between the products contains a cycle.") 55 } 56 57 // construct the final schedule 58 for _, product := range order { 59 for _, spec := range specMap[product] { 60 schedule = append(schedule, spec) 61 } 62 } 63 64 return schedule, nil 65 } 66 67 func topologicalOrdering(graph map[string]map[string]struct{}) ([]string, error) { 68 var order []string 69 // get all nodes in the graph and sort lexicographically for deterministic order 70 var nodes []string 71 indeg := make(map[string]int) 72 for node := range graph { 73 indeg[node] = 0 74 nodes = append(nodes, node) 75 } 76 sort.Strings(nodes) 77 // compute the incoming edges on each vertex 78 for _, v := range nodes { 79 for neighbor := range graph[v] { 80 indeg[neighbor]++ 81 } 82 } 83 // q contains all vertices with in-degree zero 84 var q []string 85 for _, v := range nodes { 86 if indeg[v] == 0 { 87 q = append(q, v) 88 } 89 } 90 for len(q) > 0 { 91 cur := q[0] 92 q = q[1:] 93 order = append(order, cur) 94 var neighbors []string 95 // sort all the neighbours to ensure deterministic order 96 for neighbor := range graph[cur] { 97 neighbors = append(neighbors, neighbor) 98 } 99 sort.Strings(neighbors) 100 for _, neighbor := range neighbors { 101 indeg[neighbor]-- 102 if indeg[neighbor] == 0 { 103 q = append(q, neighbor) 104 } 105 } 106 } 107 if len(order) != len(graph) { 108 return nil, errors.New("Error generating an ordering. Provided DAG contains cyclic dependencies.") 109 } 110 return order, nil 111 } 112 113 func hasDockerDep(deps map[params.DockerDepType]string) bool { 114 for depType := range deps { 115 if depType == params.DockerDepDocker { 116 return true 117 } 118 } 119 return false 120 }