-
Notifications
You must be signed in to change notification settings - Fork 288
/
config.go
142 lines (122 loc) · 3.46 KB
/
config.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
package dockercompose
import (
"strconv"
"strings"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
)
// Go representations of docker-compose.yml
// (Add fields as we need to support more things)
type Config struct {
Services map[string]ServiceConfig
}
// We use a custom Unmarshal method here so that we can store the RawYAML in addition
// to unmarshaling the fields we care about into structs.
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
aux := struct {
Services map[string]interface{} `yaml:"services"`
}{}
err := unmarshal(&aux)
if err != nil {
return err
}
if c.Services == nil {
c.Services = make(map[string]ServiceConfig)
}
for k, v := range aux.Services {
b, err := yaml.Marshal(v) // so we can unmarshal it again
if err != nil {
return err
}
svcConf := &ServiceConfig{}
err = yaml.Unmarshal(b, svcConf)
if err != nil {
return err
}
svcConf.RawYAML = b
c.Services[k] = *svcConf
}
return nil
}
type ServiceConfig struct {
RawYAML []byte // We store this to diff against when docker-compose.yml is edited to see if the manifest has changed
Build BuildConfig `yaml:"build"`
Image string `yaml:"image"`
Volumes Volumes `yaml:"volumes"`
Ports Ports `yaml:"ports"`
}
type BuildConfig struct {
Context string `yaml:"context"`
Dockerfile string `yaml:"dockerfile"`
}
type Volumes []Volume
type Volume struct {
Source string
}
func (v *Volumes) UnmarshalYAML(unmarshal func(interface{}) error) error {
var sliceType []interface{}
err := unmarshal(&sliceType)
if err != nil {
return errors.Wrap(err, "unmarshalling volumes")
}
for _, volume := range sliceType {
// Volumes syntax documented here: https://docs.docker.com/compose/compose-file/#volumes
// This implementation far from comprehensive. It will silently ignore:
// 1. "short" syntax using volume keys instead of paths
// 2. all "long" syntax volumes
// Ideally, we'd let the user know we didn't handle their case, but getting a ctx here is not easy
switch a := volume.(type) {
case string:
parts := strings.Split(a, ":")
*v = append(*v, Volume{Source: parts[0]})
}
}
return nil
}
type Ports []Port
type Port struct {
Published int `yaml:"published"`
}
func (p *Ports) UnmarshalYAML(unmarshal func(interface{}) error) error {
var sliceType []interface{}
err := unmarshal(&sliceType)
if err != nil {
return errors.Wrap(err, "unmarshalling ports")
}
for _, portSpec := range sliceType {
// Port syntax documented here:
// https://docs.docker.com/compose/compose-file/#ports
// ports aren't critical, so on any error we want to continue quietly.
//
// Fortunately, `docker-compose config` does a lot of normalization for us,
// like resolving port ranges and ensuring the protocol (tcp vs udp)
// is always included.
switch portSpec := portSpec.(type) {
case string:
withoutProtocol := strings.Split(portSpec, "/")[0]
parts := strings.Split(withoutProtocol, ":")
publishedPart := parts[0]
if len(parts) == 3 {
// For "127.0.0.1:3000:3000"
publishedPart = parts[1]
}
port, err := strconv.Atoi(publishedPart)
if err != nil {
continue
}
*p = append(*p, Port{Published: port})
case map[interface{}]interface{}:
var portStruct Port
b, err := yaml.Marshal(portSpec) // so we can unmarshal it again
if err != nil {
continue
}
err = yaml.Unmarshal(b, &portStruct)
if err != nil {
continue
}
*p = append(*p, portStruct)
}
}
return nil
}