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  }