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 }