github.com/beornf/libcompose@v0.4.1-0.20210215180846-a59802c0f07c/yaml/build.go (about) 1 package yaml 2 3 import ( 4 "errors" 5 "fmt" 6 "strconv" 7 "strings" 8 ) 9 10 // Build represents a build element in compose file. 11 // It can take multiple form in the compose file, hence this special type 12 type Build struct { 13 Context string 14 Dockerfile string 15 Args map[string]*string 16 CacheFrom []*string 17 Labels map[string]*string 18 // TODO: ShmSize (can be a string or int?) for v3.5 19 Target string 20 // Note: as of Sep 2018 this is undocumented but supported by docker-compose 21 Network string 22 } 23 24 // MarshalYAML implements the Marshaller interface. 25 func (b Build) MarshalYAML() (interface{}, error) { 26 m := map[string]interface{}{} 27 if b.Context != "" { 28 m["context"] = b.Context 29 } 30 if b.Dockerfile != "" { 31 m["dockerfile"] = b.Dockerfile 32 } 33 if len(b.Args) > 0 { 34 m["args"] = b.Args 35 } 36 if len(b.CacheFrom) > 0 { 37 m["cache_from"] = b.CacheFrom 38 } 39 if len(b.Labels) > 0 { 40 m["labels"] = b.Labels 41 } 42 if b.Target != "" { 43 m["target"] = b.Target 44 } 45 if b.Network != "" { 46 m["network"] = b.Network 47 } 48 return m, nil 49 } 50 51 // UnmarshalYAML implements the Unmarshaller interface. 52 func (b *Build) UnmarshalYAML(unmarshal func(interface{}) error) error { 53 var stringType string 54 if err := unmarshal(&stringType); err == nil { 55 b.Context = stringType 56 return nil 57 } 58 59 var mapType map[interface{}]interface{} 60 if err := unmarshal(&mapType); err == nil { 61 for mapKey, mapValue := range mapType { 62 switch mapKey { 63 case "context": 64 b.Context = mapValue.(string) 65 case "dockerfile": 66 b.Dockerfile = mapValue.(string) 67 case "args": 68 args, err := handleBuildArgs(mapValue) 69 if err != nil { 70 return err 71 } 72 b.Args = args 73 case "cache_from": 74 cacheFrom, err := handleBuildCacheFrom(mapValue) 75 if err != nil { 76 return err 77 } 78 b.CacheFrom = cacheFrom 79 case "labels": 80 labels, err := handleBuildLabels(mapValue) 81 if err != nil { 82 return err 83 } 84 b.Labels = labels 85 case "target": 86 b.Target = mapValue.(string) 87 case "network": 88 b.Network = mapValue.(string) 89 default: 90 // Ignore unknown keys 91 continue 92 } 93 } 94 return nil 95 } 96 97 return errors.New("Failed to unmarshal Build") 98 } 99 100 func handleBuildArgs(value interface{}) (map[string]*string, error) { 101 var args map[string]*string 102 switch v := value.(type) { 103 case map[interface{}]interface{}: 104 return handleBuildOptionMap(v) 105 case []interface{}: 106 return handleBuildArgsSlice(v) 107 default: 108 return args, fmt.Errorf("Failed to unmarshal Build args: %#v", value) 109 } 110 } 111 112 func handleBuildCacheFrom(value interface{}) ([]*string, error) { 113 var cacheFrom []*string 114 switch v := value.(type) { 115 case []interface{}: 116 return handleBuildCacheFromSlice(v) 117 default: 118 return cacheFrom, fmt.Errorf("Failed to unmarshal Build cache_from: %#v", value) 119 } 120 } 121 122 func handleBuildLabels(value interface{}) (map[string]*string, error) { 123 var labels map[string]*string 124 switch v := value.(type) { 125 case map[interface{}]interface{}: 126 return handleBuildOptionMap(v) 127 default: 128 return labels, fmt.Errorf("Failed to unmarshal Build labels: %#v", value) 129 } 130 } 131 132 func handleBuildCacheFromSlice(s []interface{}) ([]*string, error) { 133 var args = []*string{} 134 for _, arg := range s { 135 strArg := arg.(string) 136 args = append(args, &strArg) 137 } 138 return args, nil 139 } 140 141 func handleBuildArgsSlice(s []interface{}) (map[string]*string, error) { 142 var args = map[string]*string{} 143 for _, arg := range s { 144 // check if a value is provided 145 switch v := strings.SplitN(arg.(string), "=", 2); len(v) { 146 case 1: 147 // if we have not specified a a value for this build arg, we assign it an ascii null value and query the environment 148 // later when we build the service 149 str := "\x00" 150 args[v[0]] = &str 151 case 2: 152 // if we do have a value provided, we use it 153 args[v[0]] = &v[1] 154 } 155 } 156 return args, nil 157 } 158 159 // Used for args and labels 160 func handleBuildOptionMap(m map[interface{}]interface{}) (map[string]*string, error) { 161 args := map[string]*string{} 162 for mapKey, mapValue := range m { 163 var argValue string 164 name, ok := mapKey.(string) 165 if !ok { 166 return args, fmt.Errorf("Cannot unmarshal '%v' to type %T into a string value", name, name) 167 } 168 switch a := mapValue.(type) { 169 case string: 170 argValue = a 171 case int: 172 argValue = strconv.Itoa(a) 173 case int64: 174 argValue = strconv.Itoa(int(a)) 175 default: 176 return args, fmt.Errorf("Cannot unmarshal '%v' to type %T into a string value", mapValue, mapValue) 177 } 178 args[name] = &argValue 179 } 180 return args, nil 181 }