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 }