github.com/rakutentech/cli@v6.12.5-0.20151006231303-24468b65536e+incompatible/cf/manifest/manifest.go (about)

     1  package manifest
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  	"regexp"
     7  	"strconv"
     8  	"strings"
     9  
    10  	. "github.com/cloudfoundry/cli/cf/i18n"
    11  
    12  	"github.com/cloudfoundry/cli/cf/errors"
    13  	"github.com/cloudfoundry/cli/cf/formatters"
    14  	"github.com/cloudfoundry/cli/cf/models"
    15  	"github.com/cloudfoundry/cli/generic"
    16  	"github.com/cloudfoundry/cli/words/generator"
    17  )
    18  
    19  type Manifest struct {
    20  	Path string
    21  	Data generic.Map
    22  }
    23  
    24  func NewEmptyManifest() (m *Manifest) {
    25  	return &Manifest{Data: generic.NewMap()}
    26  }
    27  
    28  func (m Manifest) Applications() (apps []models.AppParams, err error) {
    29  	rawData, errs := expandProperties(m.Data, generator.NewWordGenerator())
    30  	if len(errs) > 0 {
    31  		err = errors.NewWithSlice(errs)
    32  		return
    33  	}
    34  
    35  	data := generic.NewMap(rawData)
    36  	appMaps, errs := m.getAppMaps(data)
    37  	if len(errs) > 0 {
    38  		err = errors.NewWithSlice(errs)
    39  		return
    40  	}
    41  
    42  	for _, appMap := range appMaps {
    43  		app, errs := mapToAppParams(filepath.Dir(m.Path), appMap)
    44  		if len(errs) > 0 {
    45  			err = errors.NewWithSlice(errs)
    46  			continue
    47  		}
    48  
    49  		apps = append(apps, app)
    50  	}
    51  
    52  	return
    53  }
    54  
    55  func (m Manifest) getAppMaps(data generic.Map) (apps []generic.Map, errs []error) {
    56  	globalProperties := data.Except([]interface{}{"applications"})
    57  
    58  	if data.Has("applications") {
    59  		appMaps, ok := data.Get("applications").([]interface{})
    60  		if !ok {
    61  			errs = append(errs, errors.New(T("Expected applications to be a list")))
    62  			return
    63  		}
    64  
    65  		for _, appData := range appMaps {
    66  			if !generic.IsMappable(appData) {
    67  				errs = append(errs, errors.NewWithFmt(T("Expected application to be a list of key/value pairs\nError occurred in manifest near:\n'{{.YmlSnippet}}'",
    68  					map[string]interface{}{"YmlSnippet": appData})))
    69  				continue
    70  			}
    71  
    72  			appMap := generic.DeepMerge(globalProperties, generic.NewMap(appData))
    73  			apps = append(apps, appMap)
    74  		}
    75  	} else {
    76  		apps = append(apps, globalProperties)
    77  	}
    78  
    79  	return
    80  }
    81  
    82  var propertyRegex = regexp.MustCompile(`\${[\w-]+}`)
    83  
    84  func expandProperties(input interface{}, babbler generator.WordGenerator) (output interface{}, errs []error) {
    85  	switch input := input.(type) {
    86  	case string:
    87  		match := propertyRegex.FindStringSubmatch(input)
    88  		if match != nil {
    89  			if match[0] == "${random-word}" {
    90  				output = strings.Replace(input, "${random-word}", strings.ToLower(babbler.Babble()), -1)
    91  			} else {
    92  				err := errors.NewWithFmt(T("Property '{{.PropertyName}}' found in manifest. This feature is no longer supported. Please remove it and try again.",
    93  					map[string]interface{}{"PropertyName": match[0]}))
    94  				errs = append(errs, err)
    95  			}
    96  		} else {
    97  			output = input
    98  		}
    99  	case []interface{}:
   100  		outputSlice := make([]interface{}, len(input))
   101  		for index, item := range input {
   102  			itemOutput, itemErrs := expandProperties(item, babbler)
   103  			outputSlice[index] = itemOutput
   104  			errs = append(errs, itemErrs...)
   105  		}
   106  		output = outputSlice
   107  	case map[interface{}]interface{}:
   108  		outputMap := make(map[interface{}]interface{})
   109  		for key, value := range input {
   110  			itemOutput, itemErrs := expandProperties(value, babbler)
   111  			outputMap[key] = itemOutput
   112  			errs = append(errs, itemErrs...)
   113  		}
   114  		output = outputMap
   115  	case generic.Map:
   116  		outputMap := generic.NewMap()
   117  		generic.Each(input, func(key, value interface{}) {
   118  			itemOutput, itemErrs := expandProperties(value, babbler)
   119  			outputMap.Set(key, itemOutput)
   120  			errs = append(errs, itemErrs...)
   121  		})
   122  		output = outputMap
   123  	default:
   124  		output = input
   125  	}
   126  
   127  	return
   128  }
   129  
   130  func mapToAppParams(basePath string, yamlMap generic.Map) (appParams models.AppParams, errs []error) {
   131  	errs = checkForNulls(yamlMap)
   132  	if len(errs) > 0 {
   133  		return
   134  	}
   135  
   136  	appParams.BuildpackUrl = stringValOrDefault(yamlMap, "buildpack", &errs)
   137  	appParams.DiskQuota = bytesVal(yamlMap, "disk_quota", &errs)
   138  
   139  	domainAry := *sliceOrEmptyVal(yamlMap, "domains", &errs)
   140  	if domain := stringVal(yamlMap, "domain", &errs); domain != nil {
   141  		domainAry = append(domainAry, *domain)
   142  	}
   143  	appParams.Domains = removeDuplicatedValue(domainAry)
   144  
   145  	hostsArr := *sliceOrEmptyVal(yamlMap, "hosts", &errs)
   146  	if host := stringVal(yamlMap, "host", &errs); host != nil {
   147  		hostsArr = append(hostsArr, *host)
   148  	}
   149  	appParams.Hosts = removeDuplicatedValue(hostsArr)
   150  
   151  	appParams.Name = stringVal(yamlMap, "name", &errs)
   152  	appParams.Path = stringVal(yamlMap, "path", &errs)
   153  	appParams.StackName = stringVal(yamlMap, "stack", &errs)
   154  	appParams.Command = stringValOrDefault(yamlMap, "command", &errs)
   155  	appParams.Memory = bytesVal(yamlMap, "memory", &errs)
   156  	appParams.InstanceCount = intVal(yamlMap, "instances", &errs)
   157  	appParams.HealthCheckTimeout = intVal(yamlMap, "timeout", &errs)
   158  	appParams.NoRoute = boolVal(yamlMap, "no-route", &errs)
   159  	appParams.NoHostname = boolVal(yamlMap, "no-hostname", &errs)
   160  	appParams.UseRandomHostname = boolVal(yamlMap, "random-route", &errs)
   161  	appParams.ServicesToBind = sliceOrEmptyVal(yamlMap, "services", &errs)
   162  	appParams.EnvironmentVars = envVarOrEmptyMap(yamlMap, &errs)
   163  
   164  	if appParams.Path != nil {
   165  		path := *appParams.Path
   166  		if filepath.IsAbs(path) {
   167  			path = filepath.Clean(path)
   168  		} else {
   169  			path = filepath.Join(basePath, path)
   170  		}
   171  		appParams.Path = &path
   172  	}
   173  
   174  	return
   175  }
   176  
   177  func removeDuplicatedValue(ary []string) *[]string {
   178  	if ary == nil {
   179  		return nil
   180  	}
   181  
   182  	m := make(map[string]bool)
   183  	for _, v := range ary {
   184  		m[v] = true
   185  	}
   186  
   187  	newAry := []string{}
   188  	for k, _ := range m {
   189  		newAry = append(newAry, k)
   190  	}
   191  	return &newAry
   192  }
   193  
   194  func checkForNulls(yamlMap generic.Map) (errs []error) {
   195  	generic.Each(yamlMap, func(key interface{}, value interface{}) {
   196  		if key == "command" || key == "buildpack" {
   197  			return
   198  		}
   199  		if value == nil {
   200  			errs = append(errs, errors.NewWithFmt(T("{{.PropertyName}} should not be null", map[string]interface{}{"PropertyName": key})))
   201  		}
   202  	})
   203  
   204  	return
   205  }
   206  
   207  func stringVal(yamlMap generic.Map, key string, errs *[]error) *string {
   208  	val := yamlMap.Get(key)
   209  	if val == nil {
   210  		return nil
   211  	}
   212  	result, ok := val.(string)
   213  	if !ok {
   214  		*errs = append(*errs, errors.NewWithFmt(T("{{.PropertyName}} must be a string value", map[string]interface{}{"PropertyName": key})))
   215  		return nil
   216  	}
   217  	return &result
   218  }
   219  
   220  func stringValOrDefault(yamlMap generic.Map, key string, errs *[]error) *string {
   221  	if !yamlMap.Has(key) {
   222  		return nil
   223  	}
   224  	empty := ""
   225  	switch val := yamlMap.Get(key).(type) {
   226  	case string:
   227  		if val == "default" {
   228  			return &empty
   229  		} else {
   230  			return &val
   231  		}
   232  	case nil:
   233  		return &empty
   234  	default:
   235  		*errs = append(*errs, errors.NewWithFmt(T("{{.PropertyName}} must be a string or null value", map[string]interface{}{"PropertyName": key})))
   236  		return nil
   237  	}
   238  }
   239  
   240  func bytesVal(yamlMap generic.Map, key string, errs *[]error) *int64 {
   241  	yamlVal := yamlMap.Get(key)
   242  	if yamlVal == nil {
   243  		return nil
   244  	}
   245  
   246  	stringVal := coerceToString(yamlVal)
   247  	value, err := formatters.ToMegabytes(stringVal)
   248  	if err != nil {
   249  		*errs = append(*errs, errors.NewWithFmt(T("Invalid value for '{{.PropertyName}}': {{.StringVal}}\n{{.Error}}",
   250  			map[string]interface{}{
   251  				"PropertyName": key,
   252  				"Error":        err.Error(),
   253  				"StringVal":    stringVal,
   254  			})))
   255  		return nil
   256  	}
   257  	return &value
   258  }
   259  
   260  func intVal(yamlMap generic.Map, key string, errs *[]error) *int {
   261  	var (
   262  		intVal int
   263  		err    error
   264  	)
   265  
   266  	switch val := yamlMap.Get(key).(type) {
   267  	case string:
   268  		intVal, err = strconv.Atoi(val)
   269  	case int:
   270  		intVal = val
   271  	case int64:
   272  		intVal = int(val)
   273  	case nil:
   274  		return nil
   275  	default:
   276  		err = errors.NewWithFmt(T("Expected {{.PropertyName}} to be a number, but it was a {{.PropertyType}}.",
   277  			map[string]interface{}{"PropertyName": key, "PropertyType": val}))
   278  	}
   279  
   280  	if err != nil {
   281  		*errs = append(*errs, err)
   282  		return nil
   283  	}
   284  
   285  	return &intVal
   286  }
   287  
   288  func coerceToString(value interface{}) string {
   289  	return fmt.Sprintf("%v", value)
   290  }
   291  
   292  func boolVal(yamlMap generic.Map, key string, errs *[]error) bool {
   293  	switch val := yamlMap.Get(key).(type) {
   294  	case nil:
   295  		return false
   296  	case bool:
   297  		return val
   298  	case string:
   299  		return val == "true"
   300  	default:
   301  		*errs = append(*errs, errors.NewWithFmt(T("Expected {{.PropertyName}} to be a boolean.", map[string]interface{}{"PropertyName": key})))
   302  		return false
   303  	}
   304  }
   305  
   306  func sliceOrEmptyVal(yamlMap generic.Map, key string, errs *[]error) *[]string {
   307  	if !yamlMap.Has(key) {
   308  		return new([]string)
   309  	}
   310  
   311  	var (
   312  		stringSlice []string
   313  		err         error
   314  	)
   315  
   316  	sliceErr := errors.NewWithFmt(T("Expected {{.PropertyName}} to be a list of strings.", map[string]interface{}{"PropertyName": key}))
   317  
   318  	switch input := yamlMap.Get(key).(type) {
   319  	case []interface{}:
   320  		for _, value := range input {
   321  			stringValue, ok := value.(string)
   322  			if !ok {
   323  				err = sliceErr
   324  				break
   325  			}
   326  			stringSlice = append(stringSlice, stringValue)
   327  		}
   328  	default:
   329  		err = sliceErr
   330  	}
   331  
   332  	if err != nil {
   333  		*errs = append(*errs, err)
   334  		return &[]string{}
   335  	}
   336  
   337  	return &stringSlice
   338  }
   339  
   340  func envVarOrEmptyMap(yamlMap generic.Map, errs *[]error) *map[string]interface{} {
   341  	key := "env"
   342  	switch envVars := yamlMap.Get(key).(type) {
   343  	case nil:
   344  		aMap := make(map[string]interface{}, 0)
   345  		return &aMap
   346  	case map[string]interface{}:
   347  		yamlMap.Set(key, generic.NewMap(yamlMap.Get(key)))
   348  		return envVarOrEmptyMap(yamlMap, errs)
   349  	case map[interface{}]interface{}:
   350  		yamlMap.Set(key, generic.NewMap(yamlMap.Get(key)))
   351  		return envVarOrEmptyMap(yamlMap, errs)
   352  	case generic.Map:
   353  		merrs := validateEnvVars(envVars)
   354  		if merrs != nil {
   355  			*errs = append(*errs, merrs...)
   356  			return nil
   357  		}
   358  
   359  		result := make(map[string]interface{}, envVars.Count())
   360  		generic.Each(envVars, func(key, value interface{}) {
   361  			result[key.(string)] = value
   362  		})
   363  
   364  		return &result
   365  	default:
   366  		*errs = append(*errs, errors.NewWithFmt(T("Expected {{.Name}} to be a set of key => value, but it was a {{.Type}}.",
   367  			map[string]interface{}{"Name": key, "Type": envVars})))
   368  		return nil
   369  	}
   370  }
   371  
   372  func validateEnvVars(input generic.Map) (errs []error) {
   373  	generic.Each(input, func(key, value interface{}) {
   374  		if value == nil {
   375  			errs = append(errs, errors.New(fmt.Sprintf(T("env var '{{.PropertyName}}' should not be null",
   376  				map[string]interface{}{"PropertyName": key}))))
   377  		}
   378  	})
   379  	return
   380  }