|
|
@@ -22,7 +22,7 @@ func AppProtoFromYaml(ctx context.Context, porterYamlBytes []byte) (*porterv1.Po
|
|
|
return nil, telemetry.Error(ctx, span, nil, "porter yaml is nil")
|
|
|
}
|
|
|
|
|
|
- porterYaml := &PorterStackYAML{}
|
|
|
+ porterYaml := &v1_PorterYAML{}
|
|
|
err := yaml.Unmarshal(porterYamlBytes, porterYaml)
|
|
|
if err != nil {
|
|
|
return nil, telemetry.Error(ctx, span, err, "error unmarshaling porter yaml")
|
|
|
@@ -59,7 +59,7 @@ func AppProtoFromYaml(ctx context.Context, porterYamlBytes []byte) (*porterv1.Po
|
|
|
if porterYaml.Apps != nil && porterYaml.Services != nil {
|
|
|
return nil, telemetry.Error(ctx, span, nil, "'apps' and 'services' are synonymous but both were defined")
|
|
|
}
|
|
|
- var services map[string]Service
|
|
|
+ var services map[string]v1_Service
|
|
|
if porterYaml.Apps != nil {
|
|
|
services = porterYaml.Apps
|
|
|
}
|
|
|
@@ -99,53 +99,7 @@ func AppProtoFromYaml(ctx context.Context, porterYamlBytes []byte) (*porterv1.Po
|
|
|
return appProto, nil
|
|
|
}
|
|
|
|
|
|
-type PorterStackYAML struct {
|
|
|
- Applications map[string]*Application `yaml:"applications" validate:"required_without=Services Apps"`
|
|
|
- Version *string `yaml:"version"`
|
|
|
- Build *Build `yaml:"build"`
|
|
|
- Env map[string]string `yaml:"env"`
|
|
|
- SyncedEnv []*SyncedEnvSection `yaml:"synced_env"`
|
|
|
- Apps map[string]Service `yaml:"apps" validate:"required_without=Applications Services"`
|
|
|
- Services map[string]Service `yaml:"services" validate:"required_without=Applications Apps"`
|
|
|
-
|
|
|
- Release *Service `yaml:"release"`
|
|
|
-}
|
|
|
-
|
|
|
-type Application struct {
|
|
|
- Services map[string]Service `yaml:"services" validate:"required"`
|
|
|
- Build *Build `yaml:"build"`
|
|
|
- Env map[string]string `yaml:"env"`
|
|
|
-
|
|
|
- Release *Service `yaml:"release"`
|
|
|
-}
|
|
|
-
|
|
|
-type Build struct {
|
|
|
- Context string `yaml:"context" validate:"dir"`
|
|
|
- Method string `yaml:"method" validate:"required,oneof=pack docker registry"`
|
|
|
- Builder string `yaml:"builder" validate:"required_if=Method pack"`
|
|
|
- Buildpacks []string `yaml:"buildpacks"`
|
|
|
- Dockerfile string `yaml:"dockerfile" validate:"required_if=Method docker"`
|
|
|
- Image string `yaml:"image" validate:"required_if=Method registry"`
|
|
|
-}
|
|
|
-
|
|
|
-type Service struct {
|
|
|
- Run string `yaml:"run"`
|
|
|
- Config map[string]interface{} `yaml:"config"`
|
|
|
- Type string `yaml:"type" validate:"required, oneof=web worker job"`
|
|
|
-}
|
|
|
-
|
|
|
-type SyncedEnvSection struct {
|
|
|
- Name string `json:"name" yaml:"name"`
|
|
|
- Version uint `json:"version" yaml:"version"`
|
|
|
- Keys []SyncedEnvSectionKey `json:"keys" yaml:"keys"`
|
|
|
-}
|
|
|
-
|
|
|
-type SyncedEnvSectionKey struct {
|
|
|
- Name string `json:"name" yaml:"name"`
|
|
|
- Secret bool `json:"secret" yaml:"secret"`
|
|
|
-}
|
|
|
-
|
|
|
-func protoEnumFromType(name string, service Service) (porterv1.ServiceType, error) {
|
|
|
+func protoEnumFromType(name string, service v1_Service) (porterv1.ServiceType, error) {
|
|
|
var serviceType porterv1.ServiceType
|
|
|
|
|
|
if service.Type != "" {
|
|
|
@@ -181,31 +135,18 @@ func protoEnumFromType(name string, service Service) (porterv1.ServiceType, erro
|
|
|
return serviceType, errors.New("no type provided and could not parse service type from name")
|
|
|
}
|
|
|
|
|
|
-func serviceProtoFromConfig(service Service, serviceType porterv1.ServiceType) (*porterv1.Service, error) {
|
|
|
- if service.Config != nil {
|
|
|
- service.Config = convertMap(service.Config).(map[string]interface{})
|
|
|
- }
|
|
|
-
|
|
|
- var instances int
|
|
|
- if service.Config != nil && service.Config["replicaCount"] != nil {
|
|
|
- parsedInstancesInt, err := convertToInt(service.Config["replicaCount"])
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("error converting instances: %w", err)
|
|
|
- }
|
|
|
- instances = parsedInstancesInt
|
|
|
+func serviceProtoFromConfig(service v1_Service, serviceType porterv1.ServiceType) (*porterv1.Service, error) {
|
|
|
+ serviceProto := &porterv1.Service{
|
|
|
+ Run: service.Run,
|
|
|
+ Type: serviceType,
|
|
|
}
|
|
|
|
|
|
- var cpuCores float32
|
|
|
- var ramMegabytes int
|
|
|
-
|
|
|
- requestsMap, err := getNestedMap(service.Config, "resources", "requests")
|
|
|
- if err == nil && requestsMap != nil {
|
|
|
- parsedCpuCores := requestsMap["cpu"]
|
|
|
- cpuCoresStr, ok := parsedCpuCores.(string)
|
|
|
- if !ok {
|
|
|
- return nil, fmt.Errorf("cpu is not a string")
|
|
|
- }
|
|
|
+ // if the revision number cannot be converted, it will default to 0
|
|
|
+ replicaCount, _ := strconv.Atoi(service.Config.ReplicaCount)
|
|
|
+ serviceProto.Instances = int32(replicaCount)
|
|
|
|
|
|
+ if service.Config.Resources.Requests.Cpu != "" {
|
|
|
+ cpuCoresStr := service.Config.Resources.Requests.Cpu
|
|
|
if !strings.HasSuffix(cpuCoresStr, "m") {
|
|
|
return nil, fmt.Errorf("cpu is not in millicores")
|
|
|
}
|
|
|
@@ -215,94 +156,29 @@ func serviceProtoFromConfig(service Service, serviceType porterv1.ServiceType) (
|
|
|
if err != nil {
|
|
|
return nil, fmt.Errorf("cpu is not a float")
|
|
|
}
|
|
|
- cpuCores = float32(cpuCoresFloat64) / 1000
|
|
|
-
|
|
|
- parsedRamMegabytes := requestsMap["memory"]
|
|
|
- ramMegabytesStr, ok := parsedRamMegabytes.(string)
|
|
|
- if !ok {
|
|
|
- return nil, fmt.Errorf("memory is not a string")
|
|
|
- }
|
|
|
-
|
|
|
- if !strings.HasSuffix(ramMegabytesStr, "Mi") {
|
|
|
- return nil, fmt.Errorf("memory is not in Mi")
|
|
|
- }
|
|
|
-
|
|
|
- ramMegabytesStr = strings.TrimSuffix(ramMegabytesStr, "Mi")
|
|
|
- ramMegabytesInt, err := strconv.Atoi(ramMegabytesStr)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("memory is not an int")
|
|
|
- }
|
|
|
- ramMegabytes = ramMegabytesInt
|
|
|
+ serviceProto.CpuCores = float32(cpuCoresFloat64) / 1000
|
|
|
}
|
|
|
|
|
|
- var port int
|
|
|
- containerMap, err := getNestedMap(service.Config, "container")
|
|
|
- if err == nil && containerMap != nil {
|
|
|
- parsedPort := containerMap["port"]
|
|
|
- portStr, ok := parsedPort.(string)
|
|
|
- if !ok {
|
|
|
- return nil, fmt.Errorf("port is not a string")
|
|
|
+ if service.Config.Resources.Requests.Memory != "" {
|
|
|
+ memoryStr := service.Config.Resources.Requests.Memory
|
|
|
+ if !strings.HasSuffix(memoryStr, "Mi") {
|
|
|
+ return nil, fmt.Errorf("memory is not in Mi")
|
|
|
}
|
|
|
|
|
|
- portInt, err := strconv.Atoi(portStr)
|
|
|
+ memoryStr = strings.TrimSuffix(memoryStr, "Mi")
|
|
|
+ memoryFloat64, err := strconv.ParseFloat(memoryStr, 32)
|
|
|
if err != nil {
|
|
|
- return nil, fmt.Errorf("port is not an int")
|
|
|
+ return nil, fmt.Errorf("memory is not a float")
|
|
|
}
|
|
|
-
|
|
|
- port = portInt
|
|
|
+ serviceProto.RamMegabytes = int32(memoryFloat64)
|
|
|
}
|
|
|
|
|
|
- autoscalingMap, err := getNestedMap(service.Config, "autoscaling")
|
|
|
- autoscalingExists := err == nil && autoscalingMap != nil
|
|
|
- var autoscalingEnabled bool
|
|
|
- var autoscalingMinInstances int
|
|
|
- var autoscalingMaxInstances int
|
|
|
- var autoscalingCpuThresholdPercent int
|
|
|
- var autoscalingMemoryThresholdPercent int
|
|
|
- if autoscalingExists {
|
|
|
- parsedEnabled := autoscalingMap["enabled"]
|
|
|
- parsedEnabledBool, err := convertToBool(parsedEnabled)
|
|
|
+ if service.Config.Container.Port != "" {
|
|
|
+ port, err := strconv.Atoi(service.Config.Container.Port)
|
|
|
if err != nil {
|
|
|
- return nil, fmt.Errorf("error converting autoscaling enabled: %w", err)
|
|
|
+ return nil, fmt.Errorf("invalid port '%s'", service.Config.Container.Port)
|
|
|
}
|
|
|
- autoscalingEnabled = parsedEnabledBool
|
|
|
-
|
|
|
- parsedMinInstances := autoscalingMap["minReplicas"]
|
|
|
- parsedMinInstancesInt, err := convertToInt(parsedMinInstances)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("error converting autoscaling min instances: %w", err)
|
|
|
- }
|
|
|
- autoscalingMinInstances = parsedMinInstancesInt
|
|
|
-
|
|
|
- parsedMaxInstances := autoscalingMap["maxReplicas"]
|
|
|
- parsedMaxInstancesInt, err := convertToInt(parsedMaxInstances)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("error converting autoscaling max instances: %w", err)
|
|
|
- }
|
|
|
- autoscalingMaxInstances = parsedMaxInstancesInt
|
|
|
-
|
|
|
- parsedCpuThresholdPercent := autoscalingMap["targetCPUUtilizationPercentage"]
|
|
|
- parsedCpuThresholdPercentInt, err := convertToInt(parsedCpuThresholdPercent)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("error converting autoscaling cpu threshold percent: %w", err)
|
|
|
- }
|
|
|
- autoscalingCpuThresholdPercent = parsedCpuThresholdPercentInt
|
|
|
-
|
|
|
- parsedMemoryThresholdPercent := autoscalingMap["targetMemoryUtilizationPercentage"]
|
|
|
- parsedMemoryThresholdPercentInt, err := convertToInt(parsedMemoryThresholdPercent)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("error converting autoscaling memory threshold percent: %w", err)
|
|
|
- }
|
|
|
- autoscalingMemoryThresholdPercent = parsedMemoryThresholdPercentInt
|
|
|
- }
|
|
|
-
|
|
|
- serviceProto := &porterv1.Service{
|
|
|
- Run: service.Run,
|
|
|
- Type: serviceType,
|
|
|
- Instances: int32(instances),
|
|
|
- CpuCores: cpuCores,
|
|
|
- RamMegabytes: int32(ramMegabytes),
|
|
|
- Port: int32(port),
|
|
|
+ serviceProto.Port = int32(port)
|
|
|
}
|
|
|
|
|
|
switch serviceType {
|
|
|
@@ -314,81 +190,40 @@ func serviceProtoFromConfig(service Service, serviceType porterv1.ServiceType) (
|
|
|
webConfig := &porterv1.WebServiceConfig{}
|
|
|
|
|
|
var autoscaling *porterv1.Autoscaling
|
|
|
- if autoscalingExists && autoscalingEnabled {
|
|
|
+ if service.Config.Autoscaling != nil && service.Config.Autoscaling.Enabled {
|
|
|
autoscaling = &porterv1.Autoscaling{
|
|
|
- Enabled: autoscalingEnabled,
|
|
|
- MinInstances: int32(autoscalingMinInstances),
|
|
|
- MaxInstances: int32(autoscalingMaxInstances),
|
|
|
- CpuThresholdPercent: int32(autoscalingCpuThresholdPercent),
|
|
|
- MemoryThresholdPercent: int32(autoscalingMemoryThresholdPercent),
|
|
|
+ Enabled: service.Config.Autoscaling.Enabled,
|
|
|
}
|
|
|
+ minReplicas, _ := strconv.Atoi(service.Config.Autoscaling.MinReplicas)
|
|
|
+ autoscaling.MinInstances = int32(minReplicas)
|
|
|
+ maxReplicas, _ := strconv.Atoi(service.Config.Autoscaling.MaxReplicas)
|
|
|
+ autoscaling.MaxInstances = int32(maxReplicas)
|
|
|
+ cpuThresholdPercent, _ := strconv.Atoi(service.Config.Autoscaling.TargetCPUUtilizationPercentage)
|
|
|
+ autoscaling.CpuThresholdPercent = int32(cpuThresholdPercent)
|
|
|
+ memoryThresholdPercent, _ := strconv.Atoi(service.Config.Autoscaling.TargetMemoryUtilizationPercentage)
|
|
|
+ autoscaling.MemoryThresholdPercent = int32(memoryThresholdPercent)
|
|
|
}
|
|
|
webConfig.Autoscaling = autoscaling
|
|
|
|
|
|
- var healthCheckEnabled bool
|
|
|
- var healthCheckHttpPath string
|
|
|
-
|
|
|
- // note that we are only reading from the readiness probe config, since readiness and liveness share the same config now
|
|
|
- readinessProbeMap, err := getNestedMap(service.Config, "health", "readinessProbe")
|
|
|
- healthCheckExists := err == nil && readinessProbeMap != nil
|
|
|
- if healthCheckExists {
|
|
|
- parsedHealthCheckEnabled := readinessProbeMap["enabled"]
|
|
|
- parsedHealthCheckEnabledBool, err := convertToBool(parsedHealthCheckEnabled)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("error converting health check enabled: %w", err)
|
|
|
- }
|
|
|
- healthCheckEnabled = parsedHealthCheckEnabledBool
|
|
|
-
|
|
|
- parsedHealthCheckHttpPath := readinessProbeMap["path"]
|
|
|
- parsedHealthCheckHttpPathStr, err := convertToString(parsedHealthCheckHttpPath)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("error converting health check http path: %w", err)
|
|
|
- }
|
|
|
- healthCheckHttpPath = parsedHealthCheckHttpPathStr
|
|
|
- }
|
|
|
-
|
|
|
var healthCheck *porterv1.HealthCheck
|
|
|
- if healthCheckExists {
|
|
|
+ // note that we are only reading from the readiness probe config, since readiness and liveness share the same config now
|
|
|
+ if service.Config.Health != nil {
|
|
|
healthCheck = &porterv1.HealthCheck{
|
|
|
- Enabled: healthCheckEnabled,
|
|
|
- HttpPath: healthCheckHttpPath,
|
|
|
+ Enabled: service.Config.Health.ReadinessProbe.Enabled,
|
|
|
+ HttpPath: service.Config.Health.ReadinessProbe.Path,
|
|
|
}
|
|
|
}
|
|
|
webConfig.HealthCheck = healthCheck
|
|
|
|
|
|
- ingressMap, err := getNestedMap(service.Config, "ingress")
|
|
|
- ingressExists := err == nil && ingressMap != nil
|
|
|
- var ingressEnabled bool
|
|
|
- if ingressExists {
|
|
|
- parsedIngressEnabled := ingressMap["enabled"]
|
|
|
- parsedIngressEnabledBool, err := convertToBool(parsedIngressEnabled)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("error converting ingress enabled: %w", err)
|
|
|
- }
|
|
|
- ingressEnabled = parsedIngressEnabledBool
|
|
|
- }
|
|
|
- webConfig.Private = !ingressEnabled
|
|
|
-
|
|
|
- if ingressExists && ingressEnabled {
|
|
|
- domains := make([]*porterv1.Domain, 0)
|
|
|
- customDomains := ingressMap["hosts"]
|
|
|
- if customDomains != nil {
|
|
|
- customDomainsArr, ok := customDomains.([]interface{})
|
|
|
- if !ok {
|
|
|
- return nil, fmt.Errorf("error converting custom domains to array")
|
|
|
- }
|
|
|
- for _, domain := range customDomainsArr {
|
|
|
- domainStr, ok := domain.(string)
|
|
|
- if !ok {
|
|
|
- return nil, fmt.Errorf("error converting custom domain to string")
|
|
|
- }
|
|
|
- domains = append(domains, &porterv1.Domain{
|
|
|
- Name: domainStr,
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
- webConfig.Domains = domains
|
|
|
+ domains := make([]*porterv1.Domain, 0)
|
|
|
+ for _, domain := range service.Config.Ingress.Hosts {
|
|
|
+ hostName := domain
|
|
|
+ domains = append(domains, &porterv1.Domain{
|
|
|
+ Name: hostName,
|
|
|
+ })
|
|
|
}
|
|
|
+ webConfig.Domains = domains
|
|
|
+ webConfig.Private = !service.Config.Ingress.Enabled
|
|
|
|
|
|
serviceProto.Config = &porterv1.Service_WebConfig{
|
|
|
WebConfig: webConfig,
|
|
|
@@ -397,14 +232,18 @@ func serviceProtoFromConfig(service Service, serviceType porterv1.ServiceType) (
|
|
|
workerConfig := &porterv1.WorkerServiceConfig{}
|
|
|
|
|
|
var autoscaling *porterv1.Autoscaling
|
|
|
- if autoscalingExists && autoscalingEnabled {
|
|
|
+ if service.Config.Autoscaling != nil && service.Config.Autoscaling.Enabled {
|
|
|
autoscaling = &porterv1.Autoscaling{
|
|
|
- Enabled: autoscalingEnabled,
|
|
|
- MinInstances: int32(autoscalingMinInstances),
|
|
|
- MaxInstances: int32(autoscalingMaxInstances),
|
|
|
- CpuThresholdPercent: int32(autoscalingCpuThresholdPercent),
|
|
|
- MemoryThresholdPercent: int32(autoscalingMemoryThresholdPercent),
|
|
|
+ Enabled: service.Config.Autoscaling.Enabled,
|
|
|
}
|
|
|
+ minReplicas, _ := strconv.Atoi(service.Config.Autoscaling.MinReplicas)
|
|
|
+ autoscaling.MinInstances = int32(minReplicas)
|
|
|
+ maxReplicas, _ := strconv.Atoi(service.Config.Autoscaling.MaxReplicas)
|
|
|
+ autoscaling.MaxInstances = int32(maxReplicas)
|
|
|
+ cpuThresholdPercent, _ := strconv.Atoi(service.Config.Autoscaling.TargetCPUUtilizationPercentage)
|
|
|
+ autoscaling.CpuThresholdPercent = int32(cpuThresholdPercent)
|
|
|
+ memoryThresholdPercent, _ := strconv.Atoi(service.Config.Autoscaling.TargetMemoryUtilizationPercentage)
|
|
|
+ autoscaling.MemoryThresholdPercent = int32(memoryThresholdPercent)
|
|
|
}
|
|
|
workerConfig.Autoscaling = autoscaling
|
|
|
|
|
|
@@ -412,29 +251,9 @@ func serviceProtoFromConfig(service Service, serviceType porterv1.ServiceType) (
|
|
|
WorkerConfig: workerConfig,
|
|
|
}
|
|
|
case porterv1.ServiceType_SERVICE_TYPE_JOB:
|
|
|
- var allowConcurrent bool
|
|
|
- if service.Config != nil && service.Config["allowConcurrent"] != nil {
|
|
|
- parsedAllowConcurrentBool, err := convertToBool(service.Config["allowConcurrent"])
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("error converting allow concurrency: %w", err)
|
|
|
- }
|
|
|
- allowConcurrent = parsedAllowConcurrentBool
|
|
|
- }
|
|
|
-
|
|
|
- var cron string
|
|
|
- cronScheduleMap, err := getNestedMap(service.Config, "schedule")
|
|
|
- if err == nil && cronScheduleMap != nil {
|
|
|
- parsedCron := cronScheduleMap["value"]
|
|
|
- parsedConString, err := convertToString(parsedCron)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("error converting cron schedule: %w", err)
|
|
|
- }
|
|
|
- cron = parsedConString
|
|
|
- }
|
|
|
-
|
|
|
jobConfig := &porterv1.JobServiceConfig{
|
|
|
- AllowConcurrent: allowConcurrent,
|
|
|
- Cron: cron,
|
|
|
+ AllowConcurrent: service.Config.AllowConcurrency,
|
|
|
+ Cron: service.Config.Schedule.Value,
|
|
|
}
|
|
|
|
|
|
serviceProto.Config = &porterv1.Service_JobConfig{
|
|
|
@@ -444,95 +263,3 @@ func serviceProtoFromConfig(service Service, serviceType porterv1.ServiceType) (
|
|
|
|
|
|
return serviceProto, nil
|
|
|
}
|
|
|
-
|
|
|
-func getNestedMap(obj map[string]interface{}, fields ...string) (map[string]interface{}, error) {
|
|
|
- var res map[string]interface{}
|
|
|
- curr := obj
|
|
|
-
|
|
|
- for _, field := range fields {
|
|
|
- objField, ok := curr[field]
|
|
|
-
|
|
|
- if !ok {
|
|
|
- return nil, fmt.Errorf("%s does not exist in object", field)
|
|
|
- }
|
|
|
-
|
|
|
- res, ok = objField.(map[string]interface{})
|
|
|
-
|
|
|
- if !ok {
|
|
|
- return nil, fmt.Errorf("%s is not a nested object", field)
|
|
|
- }
|
|
|
-
|
|
|
- curr = res
|
|
|
- }
|
|
|
-
|
|
|
- return res, nil
|
|
|
-}
|
|
|
-
|
|
|
-func convertToInt(input interface{}) (int, error) {
|
|
|
- if input == nil {
|
|
|
- return 0, nil
|
|
|
- }
|
|
|
-
|
|
|
- switch value := input.(type) {
|
|
|
- case int:
|
|
|
- return value, nil
|
|
|
- case string:
|
|
|
- return strconv.Atoi(value)
|
|
|
- default:
|
|
|
- return 0, fmt.Errorf("input is not an int or string")
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func convertToBool(input interface{}) (bool, error) {
|
|
|
- if input == nil {
|
|
|
- return false, nil
|
|
|
- }
|
|
|
-
|
|
|
- switch value := input.(type) {
|
|
|
- case bool:
|
|
|
- return value, nil
|
|
|
- case string:
|
|
|
- return strconv.ParseBool(value)
|
|
|
- default:
|
|
|
- return false, fmt.Errorf("input is not a bool or string")
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func convertToString(input interface{}) (string, error) {
|
|
|
- if input == nil {
|
|
|
- return "", nil
|
|
|
- }
|
|
|
-
|
|
|
- switch value := input.(type) {
|
|
|
- case string:
|
|
|
- return value, nil
|
|
|
- default:
|
|
|
- return "", fmt.Errorf("input is not a string")
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func convertMap(m interface{}) interface{} {
|
|
|
- switch m := m.(type) {
|
|
|
- case map[string]interface{}:
|
|
|
- for k, v := range m {
|
|
|
- m[k] = convertMap(v)
|
|
|
- }
|
|
|
- case map[string]string:
|
|
|
- result := map[string]interface{}{}
|
|
|
- for k, v := range m {
|
|
|
- result[k] = v
|
|
|
- }
|
|
|
- return result
|
|
|
- case map[interface{}]interface{}:
|
|
|
- result := map[string]interface{}{}
|
|
|
- for k, v := range m {
|
|
|
- result[k.(string)] = convertMap(v)
|
|
|
- }
|
|
|
- return result
|
|
|
- case []interface{}:
|
|
|
- for i, v := range m {
|
|
|
- m[i] = convertMap(v)
|
|
|
- }
|
|
|
- }
|
|
|
- return m
|
|
|
-}
|