-
Notifications
You must be signed in to change notification settings - Fork 14
/
composefile.go
148 lines (124 loc) · 4.3 KB
/
composefile.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package shared
import (
"errors"
"fmt"
"os"
"path/filepath"
"gopkg.in/yaml.v2"
)
const (
ComposefileName = "brave-compose.yaml"
ComposefileAlias = "brave-compose.yml"
)
// ComposeService defines a service
type ComposeService struct {
Service `yaml:",inline"`
BravefileBuild *Bravefile
Bravefile string `yaml:"bravefile,omitempty"`
Build bool `yaml:"build,omitempty"`
Base bool `yaml:"base,omitempty"`
Context string `yaml:"context,omitempty"`
Depends []string `yaml:"depends_on,omitempty"`
}
// A ComposeFile maps service names to services
type ComposeFile struct {
Path string
Services map[string]*ComposeService `yaml:"services"`
}
// NewComposeFile returns a pointer to a newly created empty ComposeFile struct
func NewComposeFile() *ComposeFile {
return &ComposeFile{}
}
// Load reads a compose file from disk and loads its settings into the composeFile struct
func (composeFile *ComposeFile) Load(file string) error {
buf, err := ReadFile(file)
if err != nil {
return err
}
err = yaml.Unmarshal(buf.Bytes(), &composeFile)
if err != nil {
return err
}
// Record composefile path (later used for deploy context)
composeFile.Path = file
// Check for empty compose file
if len(composeFile.Services) == 0 {
return fmt.Errorf("no services found in composefile %q", composeFile.Path)
}
// Move to parent directory of compose file so relative Bravefile paths work
workingDir, err := filepath.Abs(filepath.Dir(composeFile.Path))
if err != nil {
return err
}
startDir, err := os.Getwd()
if err != nil {
return err
}
os.Chdir(workingDir)
defer os.Chdir(startDir)
// Upade each service with servicename and load bravefile if provided
for serviceName := range composeFile.Services {
service := composeFile.Services[serviceName]
// Override Service.Name with the key provided in brave-compose file
service.Name = serviceName
if (service.Build || service.Base) && service.Bravefile == "" {
return fmt.Errorf("cannot build image for %q without a Bravefile path", service.Name)
}
// Load Bravefile is provided - merge service settings and save build settings
if service.Bravefile != "" {
service.BravefileBuild = NewBravefile()
err = service.BravefileBuild.Load(service.Bravefile)
if err != nil {
return fmt.Errorf("failed to load bravefile %q: %s", service.Bravefile, err)
}
service.Service.Merge(&service.BravefileBuild.PlatformService)
// Take Image field from build section if none in Service section
if service.Image == "" {
service.Image = service.BravefileBuild.Image
}
}
}
return nil
}
// TopologicalOrdering returns a string array of service names that are ordered
// so that each service comes after the services it depends on.
// If a valid ordering cannot be found due to cycles in the graph an error will be returned.
func (composeFile *ComposeFile) TopologicalOrdering() (topologicalOrdering []string, err error) {
// digraph maps services to dependents
// outdegrees maps services to unfulfilled dependency count
digraph := make(map[string][]string, len(composeFile.Services))
outdegrees := make(map[string]int, len(composeFile.Services))
for service := range composeFile.Services {
digraph[service] = nil
outdegrees[service] = 0
}
for service := range composeFile.Services {
for _, dependency := range composeFile.Services[service].Depends {
_, exists := outdegrees[dependency]
if !exists {
return topologicalOrdering, fmt.Errorf("service %q depends on service %q which does not exist", service, dependency)
}
digraph[dependency] = append(digraph[dependency], service)
outdegrees[service] += 1
}
}
for progress := true; progress; {
progress = false
for service := range outdegrees {
// take service with 0 remaining unfulfilled dependencies
if outdegrees[service] == 0 {
topologicalOrdering = append(topologicalOrdering, service)
delete(outdegrees, service)
// update dependent count for any services depending on this service
for _, dependent := range digraph[service] {
outdegrees[dependent] -= 1
}
progress = true
}
}
}
if len(topologicalOrdering) < len(composeFile.Services) {
return nil, errors.New("no valid topological sorting of dependency graph found - check graph for cycles")
}
return topologicalOrdering, nil
}