github.com/asifdxtreme/cli@v6.1.3-0.20150123051144-9ead8700b4ae+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  	appParams.Domain = stringVal(yamlMap, "domain", &errs)
   139  
   140  	hostsArr := *sliceOrEmptyVal(yamlMap, "hosts", &errs)
   141  	if host := stringVal(yamlMap, "host", &errs); host != nil {
   142  		hostsArr = append(hostsArr, *host)
   143  	}
   144  	appParams.Hosts = &hostsArr
   145  
   146  	appParams.Name = stringVal(yamlMap, "name", &errs)
   147  	appParams.Path = stringVal(yamlMap, "path", &errs)
   148  	appParams.StackName = stringVal(yamlMap, "stack", &errs)
   149  	appParams.Command = stringValOrDefault(yamlMap, "command", &errs)
   150  	appParams.Memory = bytesVal(yamlMap, "memory", &errs)
   151  	appParams.InstanceCount = intVal(yamlMap, "instances", &errs)
   152  	appParams.HealthCheckTimeout = intVal(yamlMap, "timeout", &errs)
   153  	appParams.NoRoute = boolVal(yamlMap, "no-route", &errs)
   154  	appParams.UseRandomHostname = boolVal(yamlMap, "random-route", &errs)
   155  	appParams.ServicesToBind = sliceOrEmptyVal(yamlMap, "services", &errs)
   156  	appParams.EnvironmentVars = envVarOrEmptyMap(yamlMap, &errs)
   157  
   158  	if appParams.Path != nil {
   159  		path := *appParams.Path
   160  		if filepath.IsAbs(path) {
   161  			path = filepath.Clean(path)
   162  		} else {
   163  			path = filepath.Join(basePath, path)
   164  		}
   165  		appParams.Path = &path
   166  	}
   167  
   168  	return
   169  }
   170  
   171  func checkForNulls(yamlMap generic.Map) (errs []error) {
   172  	generic.Each(yamlMap, func(key interface{}, value interface{}) {
   173  		if key == "command" || key == "buildpack" {
   174  			return
   175  		}
   176  		if value == nil {
   177  			errs = append(errs, errors.NewWithFmt(T("{{.PropertyName}} should not be null", map[string]interface{}{"PropertyName": key})))
   178  		}
   179  	})
   180  
   181  	return
   182  }
   183  
   184  func stringVal(yamlMap generic.Map, key string, errs *[]error) *string {
   185  	val := yamlMap.Get(key)
   186  	if val == nil {
   187  		return nil
   188  	}
   189  	result, ok := val.(string)
   190  	if !ok {
   191  		*errs = append(*errs, errors.NewWithFmt(T("{{.PropertyName}} must be a string value", map[string]interface{}{"PropertyName": key})))
   192  		return nil
   193  	}
   194  	return &result
   195  }
   196  
   197  func stringValOrDefault(yamlMap generic.Map, key string, errs *[]error) *string {
   198  	if !yamlMap.Has(key) {
   199  		return nil
   200  	}
   201  	empty := ""
   202  	switch val := yamlMap.Get(key).(type) {
   203  	case string:
   204  		if val == "default" {
   205  			return &empty
   206  		} else {
   207  			return &val
   208  		}
   209  	case nil:
   210  		return &empty
   211  	default:
   212  		*errs = append(*errs, errors.NewWithFmt(T("{{.PropertyName}} must be a string or null value", map[string]interface{}{"PropertyName": key})))
   213  		return nil
   214  	}
   215  }
   216  
   217  func bytesVal(yamlMap generic.Map, key string, errs *[]error) *int64 {
   218  	yamlVal := yamlMap.Get(key)
   219  	if yamlVal == nil {
   220  		return nil
   221  	}
   222  
   223  	stringVal := coerceToString(yamlVal)
   224  	value, err := formatters.ToMegabytes(stringVal)
   225  	if err != nil {
   226  		*errs = append(*errs, errors.NewWithFmt(T("Invalid value for '{{.PropertyName}}': {{.StringVal}}\n{{.Error}}",
   227  			map[string]interface{}{
   228  				"PropertyName": key,
   229  				"Error":        err.Error(),
   230  				"StringVal":    stringVal,
   231  			})))
   232  		return nil
   233  	}
   234  	return &value
   235  }
   236  
   237  func intVal(yamlMap generic.Map, key string, errs *[]error) *int {
   238  	var (
   239  		intVal int
   240  		err    error
   241  	)
   242  
   243  	switch val := yamlMap.Get(key).(type) {
   244  	case string:
   245  		intVal, err = strconv.Atoi(val)
   246  	case int:
   247  		intVal = val
   248  	case int64:
   249  		intVal = int(val)
   250  	case nil:
   251  		return nil
   252  	default:
   253  		err = errors.NewWithFmt(T("Expected {{.PropertyName}} to be a number, but it was a {{.PropertyType}}.",
   254  			map[string]interface{}{"PropertyName": key, "PropertyType": val}))
   255  	}
   256  
   257  	if err != nil {
   258  		*errs = append(*errs, err)
   259  		return nil
   260  	}
   261  
   262  	return &intVal
   263  }
   264  
   265  func coerceToString(value interface{}) string {
   266  	return fmt.Sprintf("%v", value)
   267  }
   268  
   269  func boolVal(yamlMap generic.Map, key string, errs *[]error) bool {
   270  	switch val := yamlMap.Get(key).(type) {
   271  	case nil:
   272  		return false
   273  	case bool:
   274  		return val
   275  	case string:
   276  		return val == "true"
   277  	default:
   278  		*errs = append(*errs, errors.NewWithFmt(T("Expected {{.PropertyName}} to be a boolean.", map[string]interface{}{"PropertyName": key})))
   279  		return false
   280  	}
   281  }
   282  
   283  func sliceOrEmptyVal(yamlMap generic.Map, key string, errs *[]error) *[]string {
   284  	if !yamlMap.Has(key) {
   285  		return new([]string)
   286  	}
   287  
   288  	var (
   289  		stringSlice []string
   290  		err         error
   291  	)
   292  
   293  	sliceErr := errors.NewWithFmt(T("Expected {{.PropertyName}} to be a list of strings.",
   294  		map[string]interface{}{"PropertyName": key}))
   295  
   296  	switch input := yamlMap.Get(key).(type) {
   297  	case []interface{}:
   298  		for _, value := range input {
   299  			stringValue, ok := value.(string)
   300  			if !ok {
   301  				err = sliceErr
   302  				break
   303  			}
   304  			stringSlice = append(stringSlice, stringValue)
   305  		}
   306  	default:
   307  		err = sliceErr
   308  	}
   309  
   310  	if err != nil {
   311  		*errs = append(*errs, err)
   312  		return nil
   313  	}
   314  
   315  	return &stringSlice
   316  }
   317  
   318  func envVarOrEmptyMap(yamlMap generic.Map, errs *[]error) *map[string]interface{} {
   319  	key := "env"
   320  	switch envVars := yamlMap.Get(key).(type) {
   321  	case nil:
   322  		aMap := make(map[string]interface{}, 0)
   323  		return &aMap
   324  	case map[string]interface{}:
   325  		yamlMap.Set(key, generic.NewMap(yamlMap.Get(key)))
   326  		return envVarOrEmptyMap(yamlMap, errs)
   327  	case map[interface{}]interface{}:
   328  		yamlMap.Set(key, generic.NewMap(yamlMap.Get(key)))
   329  		return envVarOrEmptyMap(yamlMap, errs)
   330  	case generic.Map:
   331  		merrs := validateEnvVars(envVars)
   332  		if merrs != nil {
   333  			*errs = append(*errs, merrs...)
   334  			return nil
   335  		}
   336  
   337  		result := make(map[string]interface{}, envVars.Count())
   338  		generic.Each(envVars, func(key, value interface{}) {
   339  			result[key.(string)] = value
   340  		})
   341  
   342  		return &result
   343  	default:
   344  		*errs = append(*errs, errors.NewWithFmt(T("Expected {{.Name}} to be a set of key => value, but it was a {{.Type}}.",
   345  			map[string]interface{}{"Name": key, "Type": envVars})))
   346  		return nil
   347  	}
   348  }
   349  
   350  func validateEnvVars(input generic.Map) (errs []error) {
   351  	generic.Each(input, func(key, value interface{}) {
   352  		if value == nil {
   353  			errs = append(errs, errors.New(fmt.Sprintf(T("env var '{{.PropertyName}}' should not be null",
   354  				map[string]interface{}{"PropertyName": key}))))
   355  		}
   356  	})
   357  	return
   358  }