service.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. package validate
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. type RequestResources struct {
  7. cpu string
  8. memory string
  9. }
  10. type Resources struct {
  11. requests RequestResources
  12. }
  13. type Container struct {
  14. command string
  15. port string
  16. }
  17. type ServiceDef struct {
  18. port string
  19. }
  20. type Ingress struct {
  21. enabled bool
  22. custom_domain bool
  23. hosts []string
  24. porter_hosts []string
  25. annotations map[string]string
  26. }
  27. type HealthProbe struct {
  28. enabled bool
  29. failureThreshold int
  30. path string
  31. periodSeconds int
  32. }
  33. type HealthChecks struct {
  34. startupProbe HealthProbe
  35. livenessProbe HealthProbe
  36. readinessProbe HealthProbe
  37. }
  38. type AutoScaling struct {
  39. enabled bool
  40. minReplicas int
  41. maxReplicas int
  42. targetCPUUtilizationPercentage int
  43. targetMemoryUtilizationPercentage int
  44. }
  45. type CloudSql struct {
  46. enabled bool
  47. connectionName string
  48. dbPort int
  49. serviceAccountJSON string
  50. }
  51. type JobSchedule struct {
  52. enabled bool
  53. value string
  54. }
  55. type WebServiceConfig struct {
  56. replicaCount *string
  57. resources *Resources
  58. container *Container
  59. autoscaling *AutoScaling
  60. ingress *Ingress
  61. service *ServiceDef
  62. health *HealthChecks
  63. cloudsql *CloudSql
  64. }
  65. type WorkerServiceConfig struct {
  66. replicaCount *string
  67. container *Container
  68. resources *Resources
  69. autoscaling *AutoScaling
  70. cloudsql *CloudSql
  71. }
  72. type JobServiceConfig struct {
  73. allowConcurrent *bool
  74. container *Container
  75. resources *Resources
  76. schedule *JobSchedule
  77. paused *bool
  78. cloudsql *CloudSql
  79. }
  80. func getType(name string, service Service) string {
  81. if service.Type != "" {
  82. return service.Type
  83. }
  84. if strings.Contains(name, "web") {
  85. return "web"
  86. }
  87. if strings.Contains(name, "wkr") {
  88. return "worker"
  89. }
  90. return "job"
  91. }
  92. func HydrateService(current Service, previous Service, name string) (Service, error) {
  93. service := Service{}
  94. serviceType := getType(name, current)
  95. switch serviceType {
  96. case "web":
  97. service, err := hydrateWebService(current, previous)
  98. if err != nil {
  99. return service, err
  100. }
  101. case "worker":
  102. service, err := hydrateWorkerService(current, previous)
  103. if err != nil {
  104. return service, err
  105. }
  106. case "job":
  107. service, err := hydrateJobService(current, previous)
  108. if err != nil {
  109. return service, err
  110. }
  111. }
  112. return service, nil
  113. }
  114. func hydrateWebService(current, previous Service) (Service, error) {
  115. service := Service{
  116. Type: "web",
  117. Run: previous.Run,
  118. }
  119. if current.Run != "" {
  120. service.Run = current.Run
  121. }
  122. validatedConfig := WebServiceConfig{}
  123. currentConfig := WebServiceConfig{}
  124. // check if current.Config exists
  125. if current.Config != nil {
  126. // cast current.Config to WebServiceConfig
  127. config, ok := current.Config.(WebServiceConfig)
  128. if !ok {
  129. return service, fmt.Errorf("unable to cast current service config to web service config")
  130. }
  131. currentConfig = config
  132. }
  133. previousConfig := WebServiceConfig{}
  134. // check if previous.Config exists
  135. if previous.Config != nil {
  136. // cast previous.Config to WebServiceConfig
  137. config, ok := previous.Config.(WebServiceConfig)
  138. if !ok {
  139. return service, fmt.Errorf("unable to cast existing service config to web service config")
  140. }
  141. previousConfig = config
  142. }
  143. container, err := validateContainer(currentConfig.container, previousConfig.container)
  144. if err != nil {
  145. return service, fmt.Errorf("unable to validate container for web service: %w", err)
  146. }
  147. validatedConfig.container = container
  148. resources, err := validateResources(currentConfig.resources, previousConfig.resources)
  149. if err != nil {
  150. return service, fmt.Errorf("unable to validate resources for web service: %w", err)
  151. }
  152. validatedConfig.resources = resources
  153. autoScaling, err := validateAutoScaling(currentConfig.autoscaling, previousConfig.autoscaling)
  154. if err != nil {
  155. return service, fmt.Errorf("unable to validate autoscaling for web service: %w", err)
  156. }
  157. validatedConfig.autoscaling = autoScaling
  158. ingress, err := validateIngress(currentConfig.ingress, previousConfig.ingress)
  159. if err != nil {
  160. return service, fmt.Errorf("unable to validate ingress for web service: %w", err)
  161. }
  162. validatedConfig.ingress = ingress
  163. serviceDef, err := validateService(currentConfig.service, previousConfig.service)
  164. if err != nil {
  165. return service, fmt.Errorf("unable to validate service for web service: %w", err)
  166. }
  167. validatedConfig.service = serviceDef
  168. fmt.Printf("validatedConfig: %+v\n", validatedConfig)
  169. service.Config = validatedConfig
  170. return service, nil
  171. }
  172. func hydrateWorkerService(current, previous Service) (Service, error) {
  173. service := Service{
  174. Type: "worker",
  175. Run: previous.Run,
  176. }
  177. if current.Run != "" {
  178. service.Run = current.Run
  179. }
  180. validatedConfig := WorkerServiceConfig{}
  181. currentConfig := WorkerServiceConfig{}
  182. // check if current.Config exists
  183. if current.Config != nil {
  184. // cast current.Config to WorkerServiceConfig
  185. config, ok := current.Config.(WorkerServiceConfig)
  186. if !ok {
  187. return service, fmt.Errorf("unable to cast current config to worker service config")
  188. }
  189. currentConfig = config
  190. }
  191. previousConfig := WorkerServiceConfig{}
  192. // check if previous.Config exists
  193. if previous.Config != nil {
  194. // cast previous.Config to WorkerServiceConfig
  195. config, ok := previous.Config.(WorkerServiceConfig)
  196. if !ok {
  197. return service, fmt.Errorf("unable to cast previous config to worker service config")
  198. }
  199. previousConfig = config
  200. }
  201. container, err := validateContainer(currentConfig.container, previousConfig.container)
  202. if err != nil {
  203. return service, fmt.Errorf("unable to validate container for worker service: %w", err)
  204. }
  205. validatedConfig.container = container
  206. resources, err := validateResources(currentConfig.resources, previousConfig.resources)
  207. if err != nil {
  208. return service, fmt.Errorf("unable to validate resources for worker service: %w", err)
  209. }
  210. validatedConfig.resources = resources
  211. autoScaling, err := validateAutoScaling(currentConfig.autoscaling, previousConfig.autoscaling)
  212. if err != nil {
  213. return service, fmt.Errorf("unable to validate autoscaling for worker service: %w", err)
  214. }
  215. validatedConfig.autoscaling = autoScaling
  216. cloudsql, err := validateCloudSql(currentConfig.cloudsql, previousConfig.cloudsql)
  217. if err != nil {
  218. return service, fmt.Errorf("unable to validate cloudsql for worker service: %w", err)
  219. }
  220. validatedConfig.cloudsql = cloudsql
  221. service.Config = validatedConfig
  222. return service, nil
  223. }
  224. func hydrateJobService(current, previous Service) (Service, error) {
  225. service := Service{}
  226. return service, nil
  227. }
  228. func validateContainer(current, previous *Container) (*Container, error) {
  229. container := &Container{
  230. command: "",
  231. port: "80",
  232. }
  233. if previous != nil {
  234. container = previous
  235. }
  236. // merge current into container
  237. if current != nil {
  238. if current.command != "" {
  239. container.command = current.command
  240. }
  241. if current.port != "" {
  242. container.port = current.port
  243. }
  244. }
  245. return container, nil
  246. }
  247. func validateResources(current, previous *Resources) (*Resources, error) {
  248. resources := &Resources{
  249. requests: RequestResources{
  250. cpu: "100m",
  251. memory: "256Mi",
  252. },
  253. }
  254. if previous != nil {
  255. resources = previous
  256. }
  257. // merge current into resources
  258. if current != nil {
  259. if current.requests.cpu != "" {
  260. resources.requests.cpu = current.requests.cpu
  261. }
  262. if current.requests.memory != "" {
  263. resources.requests.memory = current.requests.memory
  264. }
  265. }
  266. return resources, nil
  267. }
  268. func validateAutoScaling(current, previous *AutoScaling) (*AutoScaling, error) {
  269. autoScaling := &AutoScaling{
  270. enabled: false,
  271. minReplicas: 1,
  272. maxReplicas: 10,
  273. targetCPUUtilizationPercentage: 50,
  274. targetMemoryUtilizationPercentage: 50,
  275. }
  276. if previous != nil {
  277. autoScaling = previous
  278. }
  279. // merge current into autoScaling
  280. if current != nil {
  281. if current.enabled {
  282. autoScaling.enabled = current.enabled
  283. }
  284. if current.minReplicas != 0 {
  285. autoScaling.minReplicas = current.minReplicas
  286. }
  287. if current.maxReplicas != 0 {
  288. autoScaling.maxReplicas = current.maxReplicas
  289. }
  290. if current.targetCPUUtilizationPercentage != 0 {
  291. autoScaling.targetCPUUtilizationPercentage = current.targetCPUUtilizationPercentage
  292. }
  293. if current.targetMemoryUtilizationPercentage != 0 {
  294. autoScaling.targetMemoryUtilizationPercentage = current.targetMemoryUtilizationPercentage
  295. }
  296. }
  297. return autoScaling, nil
  298. }
  299. func validateIngress(current, previous *Ingress) (*Ingress, error) {
  300. ingress := &Ingress{
  301. enabled: false,
  302. custom_domain: false,
  303. hosts: []string{},
  304. porter_hosts: []string{},
  305. annotations: map[string]string{},
  306. }
  307. if previous != nil {
  308. ingress = previous
  309. }
  310. // merge current into ingress
  311. if current != nil {
  312. if current.enabled {
  313. ingress.enabled = current.enabled
  314. }
  315. if current.custom_domain {
  316. ingress.custom_domain = current.custom_domain
  317. }
  318. if len(current.hosts) > 0 {
  319. ingress.hosts = current.hosts
  320. }
  321. if len(current.porter_hosts) > 0 {
  322. ingress.porter_hosts = current.porter_hosts
  323. }
  324. if len(current.annotations) > 0 {
  325. ingress.annotations = current.annotations
  326. }
  327. }
  328. return ingress, nil
  329. }
  330. func validateService(current, previous *ServiceDef) (*ServiceDef, error) {
  331. service := &ServiceDef{
  332. port: "80",
  333. }
  334. if previous != nil {
  335. service = previous
  336. }
  337. // merge current into service
  338. if current != nil {
  339. if current.port != "" {
  340. service.port = current.port
  341. }
  342. }
  343. return service, nil
  344. }
  345. func validateCloudSql(current, previous *CloudSql) (*CloudSql, error) {
  346. cloudsql := &CloudSql{
  347. enabled: false,
  348. connectionName: "",
  349. dbPort: 5432,
  350. serviceAccountJSON: "",
  351. }
  352. if previous != nil {
  353. cloudsql = previous
  354. }
  355. // merge current into cloudsql
  356. if current != nil {
  357. if current.enabled {
  358. cloudsql.enabled = current.enabled
  359. }
  360. if current.connectionName != "" {
  361. cloudsql.connectionName = current.connectionName
  362. }
  363. if current.dbPort != 0 {
  364. cloudsql.dbPort = current.dbPort
  365. }
  366. if current.serviceAccountJSON != "" {
  367. cloudsql.serviceAccountJSON = current.serviceAccountJSON
  368. }
  369. }
  370. return cloudsql, nil
  371. }