yaml.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. package v2
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "strings"
  7. porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
  8. "github.com/porter-dev/porter/internal/telemetry"
  9. "gopkg.in/yaml.v2"
  10. )
  11. // AppProtoWithEnv is a struct containing a PorterApp proto object and its environment variables
  12. type AppProtoWithEnv struct {
  13. AppProto *porterv1.PorterApp
  14. Addons []*porterv1.Addon
  15. EnvVariables map[string]string
  16. }
  17. // AppWithPreviewOverrides is a porter app definition with its preview app definition, if it exists
  18. type AppWithPreviewOverrides struct {
  19. AppProtoWithEnv
  20. PreviewApp *AppProtoWithEnv
  21. }
  22. // AppProtoFromYaml converts a Porter YAML file into a PorterApp proto object
  23. func AppProtoFromYaml(ctx context.Context, porterYamlBytes []byte) (AppWithPreviewOverrides, error) {
  24. ctx, span := telemetry.NewSpan(ctx, "v2-app-proto-from-yaml")
  25. defer span.End()
  26. var out AppWithPreviewOverrides
  27. if porterYamlBytes == nil {
  28. return out, telemetry.Error(ctx, span, nil, "porter yaml is nil")
  29. }
  30. porterYaml := &PorterYAML{}
  31. err := yaml.Unmarshal(porterYamlBytes, porterYaml)
  32. if err != nil {
  33. return out, telemetry.Error(ctx, span, err, "error unmarshaling porter yaml")
  34. }
  35. appProto, envVariables, err := ProtoFromApp(ctx, porterYaml.PorterApp)
  36. if err != nil {
  37. return out, telemetry.Error(ctx, span, err, "error converting porter yaml to proto")
  38. }
  39. out.AppProto = appProto
  40. out.EnvVariables = envVariables
  41. var addons []*porterv1.Addon
  42. for _, addon := range porterYaml.Addons {
  43. addonProto, err := ProtoFromAddon(ctx, addon)
  44. if err != nil {
  45. return out, telemetry.Error(ctx, span, err, "error converting addon to proto")
  46. }
  47. addons = append(addons, addonProto)
  48. }
  49. out.Addons = addons
  50. if porterYaml.Previews != nil {
  51. previewConfig := *porterYaml.Previews
  52. previewAppProto, previewEnvVariables, err := ProtoFromApp(ctx, previewConfig.PorterApp)
  53. if err != nil {
  54. return out, telemetry.Error(ctx, span, err, "error converting preview porter yaml to proto")
  55. }
  56. out.PreviewApp = &AppProtoWithEnv{
  57. AppProto: previewAppProto,
  58. EnvVariables: previewEnvVariables,
  59. }
  60. var previewAddons []*porterv1.Addon
  61. for _, addon := range previewConfig.Addons {
  62. addonProto, err := ProtoFromAddon(ctx, addon)
  63. if err != nil {
  64. return out, telemetry.Error(ctx, span, err, "error converting preview addon to proto")
  65. }
  66. previewAddons = append(previewAddons, addonProto)
  67. }
  68. out.PreviewApp.Addons = previewAddons
  69. }
  70. return out, nil
  71. }
  72. // ServiceType is the type of a service in a Porter YAML file
  73. type ServiceType string
  74. const (
  75. // ServiceType_Web is type for web services specified in Porter YAML
  76. ServiceType_Web ServiceType = "web"
  77. // ServiceType_Worker is type for worker services specified in Porter YAML
  78. ServiceType_Worker ServiceType = "worker"
  79. // ServiceType_Job is type for job services specified in Porter YAML
  80. ServiceType_Job ServiceType = "job"
  81. )
  82. // PorterApp represents all the possible fields in a Porter YAML file
  83. type PorterApp struct {
  84. Version string `yaml:"version,omitempty"`
  85. Name string `yaml:"name"`
  86. Services []Service `yaml:"services"`
  87. Image *Image `yaml:"image,omitempty"`
  88. Build *Build `yaml:"build,omitempty"`
  89. Env Env `yaml:"env,omitempty"`
  90. Predeploy *Service `yaml:"predeploy,omitempty"`
  91. InitialDeploy *Service `yaml:"initialDeploy,omitempty"`
  92. EnvGroups []string `yaml:"envGroups,omitempty"`
  93. EfsStorage *EfsStorage `yaml:"efsStorage,omitempty"`
  94. RequiredApps []RequiredApp `yaml:"requiredApps,omitempty"`
  95. AutoRollback *AutoRollback `yaml:"autoRollback,omitempty"`
  96. }
  97. // PorterAppWithAddons is the definition of a porter app in a Porter YAML file with addons
  98. type PorterAppWithAddons struct {
  99. PorterApp `yaml:",inline"`
  100. Addons []Addon `yaml:"addons,omitempty"`
  101. }
  102. // PorterYAML represents all the possible fields in a Porter YAML file
  103. type PorterYAML struct {
  104. PorterAppWithAddons `yaml:",inline"`
  105. Previews *PorterAppWithAddons `yaml:"previews,omitempty"`
  106. }
  107. // Addon represents an addon that should be installed alongside a Porter app
  108. type Addon struct {
  109. Name string `yaml:"name"`
  110. Type string `yaml:"type"`
  111. EnvGroups []string `yaml:"envGroups,omitempty"`
  112. CpuCores float32 `yaml:"cpuCores,omitempty"`
  113. RamMegabytes int `yaml:"ramMegabytes,omitempty"`
  114. StorageGigabytes float32 `yaml:"storageGigabytes,omitempty"`
  115. }
  116. // RequiredApp specifies another porter app that this app expects to be deployed alongside it
  117. type RequiredApp struct {
  118. Name string `yaml:"name"`
  119. FromTarget string `yaml:"fromTarget"`
  120. }
  121. // EfsStorage represents the EFS storage settings for a Porter app
  122. type EfsStorage struct {
  123. Enabled bool `yaml:"enabled"`
  124. }
  125. // AutoRollback represents the auto rollback settings for a Porter app
  126. type AutoRollback struct {
  127. Enabled bool `yaml:"enabled"`
  128. }
  129. // Build represents the build settings for a Porter app
  130. type Build struct {
  131. Context string `yaml:"context,omitempty" validate:"dir"`
  132. Method string `yaml:"method,omitempty" validate:"required,oneof=pack docker registry"`
  133. Builder string `yaml:"builder,omitempty" validate:"required_if=Method pack"`
  134. Buildpacks []string `yaml:"buildpacks,omitempty"`
  135. Dockerfile string `yaml:"dockerfile,omitempty" validate:"required_if=Method docker"`
  136. CommitSHA string `yaml:"commitSha,omitempty"`
  137. }
  138. // Image is the repository and tag for an app's build image
  139. type Image struct {
  140. Repository string `yaml:"repository"`
  141. Tag string `yaml:"tag"`
  142. }
  143. // Service represents a single service in a porter app
  144. type Service struct {
  145. Name string `yaml:"name,omitempty"`
  146. Run *string `yaml:"run,omitempty"`
  147. Type ServiceType `yaml:"type,omitempty" validate:"required, oneof=web worker job"`
  148. Instances *int32 `yaml:"instances,omitempty"`
  149. CpuCores float32 `yaml:"cpuCores,omitempty"`
  150. RamMegabytes int `yaml:"ramMegabytes,omitempty"`
  151. GpuCoresNvidia float32 `yaml:"gpuCoresNvidia,omitempty"`
  152. GPU *GPU `yaml:"gpu,omitempty"`
  153. SmartOptimization *bool `yaml:"smartOptimization,omitempty"`
  154. TerminationGracePeriodSeconds *int32 `yaml:"terminationGracePeriodSeconds,omitempty"`
  155. Port int `yaml:"port,omitempty"`
  156. Autoscaling *AutoScaling `yaml:"autoscaling,omitempty" validate:"excluded_if=Type job"`
  157. Domains []Domains `yaml:"domains,omitempty" validate:"excluded_unless=Type web"`
  158. HealthCheck *HealthCheck `yaml:"healthCheck,omitempty" validate:"excluded_unless=Type web"`
  159. AllowConcurrent *bool `yaml:"allowConcurrent,omitempty" validate:"excluded_unless=Type job"`
  160. Cron string `yaml:"cron,omitempty" validate:"excluded_unless=Type job"`
  161. SuspendCron *bool `yaml:"suspendCron,omitempty" validate:"excluded_unless=Type job"`
  162. TimeoutSeconds int `yaml:"timeoutSeconds,omitempty" validate:"excluded_unless=Type job"`
  163. Private *bool `yaml:"private,omitempty" validate:"excluded_unless=Type web"`
  164. IngressAnnotations map[string]string `yaml:"ingressAnnotations,omitempty" validate:"excluded_unless=Type web"`
  165. DisableTLS *bool `yaml:"disableTLS,omitempty" validate:"excluded_unless=Type web"`
  166. Sleep *bool `yaml:"sleep,omitempty" validate:"excluded_unless=Type job"`
  167. }
  168. // AutoScaling represents the autoscaling settings for web services
  169. type AutoScaling struct {
  170. Enabled bool `yaml:"enabled"`
  171. MinInstances int `yaml:"minInstances"`
  172. MaxInstances int `yaml:"maxInstances"`
  173. CpuThresholdPercent int `yaml:"cpuThresholdPercent"`
  174. MemoryThresholdPercent int `yaml:"memoryThresholdPercent"`
  175. }
  176. // GPU represents GPU settings for a service
  177. type GPU struct {
  178. Enabled bool `yaml:"enabled"`
  179. GpuCoresNvidia int `yaml:"gpuCoresNvidia"`
  180. }
  181. // Domains are the custom domains for a web service
  182. type Domains struct {
  183. Name string `yaml:"name"`
  184. }
  185. // HealthCheck contains the health check settings
  186. type HealthCheck struct {
  187. Enabled bool `yaml:"enabled"`
  188. HttpPath string `yaml:"httpPath,omitempty"`
  189. Command string `yaml:"command,omitempty"`
  190. TimeoutSeconds int `yaml:"timeoutSeconds,omitempty"`
  191. InitialDelaySeconds *int32 `yaml:"initialDelaySeconds,omitempty"`
  192. }
  193. // ProtoFromApp converts a PorterApp type to a base PorterApp proto type and returns env variables
  194. func ProtoFromApp(ctx context.Context, porterApp PorterApp) (*porterv1.PorterApp, map[string]string, error) {
  195. ctx, span := telemetry.NewSpan(ctx, "build-app-proto")
  196. defer span.End()
  197. appProto := &porterv1.PorterApp{
  198. Name: porterApp.Name,
  199. }
  200. if porterApp.Build != nil {
  201. appProto.Build = &porterv1.Build{
  202. Context: porterApp.Build.Context,
  203. Method: porterApp.Build.Method,
  204. Builder: porterApp.Build.Builder,
  205. Buildpacks: porterApp.Build.Buildpacks,
  206. Dockerfile: porterApp.Build.Dockerfile,
  207. CommitSha: porterApp.Build.CommitSHA,
  208. }
  209. }
  210. if porterApp.Image != nil {
  211. appProto.Image = &porterv1.AppImage{
  212. Repository: porterApp.Image.Repository,
  213. Tag: porterApp.Image.Tag,
  214. }
  215. }
  216. var services []*porterv1.Service
  217. for _, service := range porterApp.Services {
  218. serviceType := protoEnumFromType(service.Name, service)
  219. serviceProto, err := serviceProtoFromConfig(service, serviceType)
  220. if err != nil {
  221. return appProto, nil, telemetry.Error(ctx, span, err, "error casting service config")
  222. }
  223. if service.Name == "" {
  224. return appProto, nil, telemetry.Error(ctx, span, nil, "service found with no name")
  225. }
  226. services = append(services, serviceProto)
  227. }
  228. appProto.ServiceList = services
  229. if porterApp.Predeploy != nil {
  230. predeployProto, err := serviceProtoFromConfig(*porterApp.Predeploy, porterv1.ServiceType_SERVICE_TYPE_JOB)
  231. if err != nil {
  232. return appProto, nil, telemetry.Error(ctx, span, err, "error casting predeploy config")
  233. }
  234. appProto.Predeploy = predeployProto
  235. }
  236. if porterApp.InitialDeploy != nil {
  237. initialDeployProto, err := serviceProtoFromConfig(*porterApp.InitialDeploy, porterv1.ServiceType_SERVICE_TYPE_JOB)
  238. if err != nil {
  239. return appProto, nil, telemetry.Error(ctx, span, err, "error casting initial deploy config")
  240. }
  241. appProto.InitialDeploy = initialDeployProto
  242. }
  243. for _, envGroup := range porterApp.EnvGroups {
  244. appProto.EnvGroups = append(appProto.EnvGroups, &porterv1.EnvGroup{
  245. Name: envGroup,
  246. Version: 0, // this will be updated to latest when applied
  247. })
  248. }
  249. if porterApp.EfsStorage != nil {
  250. appProto.EfsStorage = &porterv1.EFS{
  251. Enabled: porterApp.EfsStorage.Enabled,
  252. }
  253. }
  254. if porterApp.AutoRollback != nil {
  255. appProto.AutoRollback = &porterv1.AutoRollback{
  256. Enabled: porterApp.AutoRollback.Enabled,
  257. }
  258. }
  259. for _, requiredApp := range porterApp.RequiredApps {
  260. var targetIdentifier *porterv1.DeploymentTargetIdentifier
  261. if requiredApp.Name == "" {
  262. return appProto, nil, telemetry.Error(ctx, span, nil, "required app specified with no name")
  263. }
  264. if requiredApp.FromTarget != "" {
  265. targetIdentifier = &porterv1.DeploymentTargetIdentifier{
  266. Name: requiredApp.FromTarget,
  267. }
  268. }
  269. appProto.RequiredApps = append(appProto.RequiredApps, &porterv1.RequiredApp{
  270. Name: requiredApp.Name,
  271. FromTarget: targetIdentifier,
  272. })
  273. }
  274. envMap := make(map[string]string)
  275. var envVariables []*porterv1.EnvVariable
  276. for _, envVar := range porterApp.Env {
  277. switch envVar.Source {
  278. case EnvVariableSource_Value:
  279. if !envVar.Value.IsSet {
  280. return appProto, nil, telemetry.Error(ctx, span, nil, "no value set for env variable")
  281. }
  282. envMap[envVar.Key] = envVar.Value.Value
  283. case EnvVariableSource_FromApp:
  284. if !envVar.FromApp.IsSet {
  285. return appProto, nil, telemetry.Error(ctx, span, nil, "no value set for env variable")
  286. }
  287. fromApp, err := EnvVarFromAppToProto(envVar.FromApp.Value)
  288. if err != nil {
  289. return appProto, nil, telemetry.Error(ctx, span, err, "error converting env variable from app to proto")
  290. }
  291. envVariables = append(envVariables, &porterv1.EnvVariable{
  292. Key: envVar.Key,
  293. Source: porterv1.EnvVariableSource_ENV_VARIABLE_SOURCE_FROM_APP,
  294. Definition: &porterv1.EnvVariable_FromApp{
  295. FromApp: fromApp,
  296. },
  297. })
  298. default:
  299. return appProto, nil, telemetry.Error(ctx, span, nil, "invalid definition for env variable")
  300. }
  301. }
  302. appProto.Env = envVariables
  303. return appProto, envMap, nil
  304. }
  305. func protoEnumFromType(name string, service Service) porterv1.ServiceType {
  306. serviceType := porterv1.ServiceType_SERVICE_TYPE_UNSPECIFIED
  307. if strings.Contains(name, "web") {
  308. serviceType = porterv1.ServiceType_SERVICE_TYPE_WEB
  309. }
  310. if strings.Contains(name, "wkr") || strings.Contains(name, "worker") {
  311. serviceType = porterv1.ServiceType_SERVICE_TYPE_WORKER
  312. }
  313. if strings.Contains(name, "job") {
  314. serviceType = porterv1.ServiceType_SERVICE_TYPE_JOB
  315. }
  316. switch service.Type {
  317. case "web":
  318. serviceType = porterv1.ServiceType_SERVICE_TYPE_WEB
  319. case "worker":
  320. serviceType = porterv1.ServiceType_SERVICE_TYPE_WORKER
  321. case "job":
  322. serviceType = porterv1.ServiceType_SERVICE_TYPE_JOB
  323. }
  324. return serviceType
  325. }
  326. func serviceProtoFromConfig(service Service, serviceType porterv1.ServiceType) (*porterv1.Service, error) {
  327. serviceProto := &porterv1.Service{
  328. Name: service.Name,
  329. RunOptional: service.Run,
  330. InstancesOptional: service.Instances,
  331. CpuCores: service.CpuCores,
  332. RamMegabytes: int32(service.RamMegabytes),
  333. GpuCoresNvidia: service.GpuCoresNvidia,
  334. Port: int32(service.Port),
  335. SmartOptimization: service.SmartOptimization,
  336. Type: serviceType,
  337. TerminationGracePeriodSeconds: service.TerminationGracePeriodSeconds,
  338. }
  339. if service.GPU != nil {
  340. gpu := &porterv1.GPU{
  341. Enabled: service.GPU.Enabled,
  342. GpuCoresNvidia: int32(service.GPU.GpuCoresNvidia),
  343. }
  344. serviceProto.Gpu = gpu
  345. }
  346. switch serviceType {
  347. default:
  348. return nil, fmt.Errorf("invalid service type '%s'", serviceType)
  349. case porterv1.ServiceType_SERVICE_TYPE_UNSPECIFIED:
  350. return nil, errors.New("Service type unspecified")
  351. case porterv1.ServiceType_SERVICE_TYPE_WEB:
  352. webConfig := &porterv1.WebServiceConfig{}
  353. var autoscaling *porterv1.Autoscaling
  354. if service.Autoscaling != nil {
  355. autoscaling = &porterv1.Autoscaling{
  356. Enabled: service.Autoscaling.Enabled,
  357. MinInstances: int32(service.Autoscaling.MinInstances),
  358. MaxInstances: int32(service.Autoscaling.MaxInstances),
  359. CpuThresholdPercent: int32(service.Autoscaling.CpuThresholdPercent),
  360. MemoryThresholdPercent: int32(service.Autoscaling.MemoryThresholdPercent),
  361. }
  362. }
  363. webConfig.Autoscaling = autoscaling
  364. var healthCheck *porterv1.HealthCheck
  365. if service.HealthCheck != nil {
  366. healthCheck = &porterv1.HealthCheck{
  367. Enabled: service.HealthCheck.Enabled,
  368. HttpPath: service.HealthCheck.HttpPath,
  369. Command: service.HealthCheck.Command,
  370. TimeoutSeconds: int32(service.HealthCheck.TimeoutSeconds),
  371. InitialDelaySeconds: service.HealthCheck.InitialDelaySeconds,
  372. }
  373. }
  374. webConfig.HealthCheck = healthCheck
  375. domains := make([]*porterv1.Domain, 0)
  376. for _, domain := range service.Domains {
  377. domains = append(domains, &porterv1.Domain{
  378. Name: domain.Name,
  379. })
  380. }
  381. webConfig.Domains = domains
  382. webConfig.IngressAnnotations = service.IngressAnnotations
  383. if service.Private != nil {
  384. webConfig.Private = service.Private
  385. }
  386. if service.DisableTLS != nil {
  387. webConfig.DisableTls = service.DisableTLS
  388. }
  389. if service.Sleep != nil {
  390. serviceProto.Sleep = service.Sleep
  391. }
  392. serviceProto.Config = &porterv1.Service_WebConfig{
  393. WebConfig: webConfig,
  394. }
  395. case porterv1.ServiceType_SERVICE_TYPE_WORKER:
  396. workerConfig := &porterv1.WorkerServiceConfig{}
  397. var autoscaling *porterv1.Autoscaling
  398. if service.Autoscaling != nil {
  399. autoscaling = &porterv1.Autoscaling{
  400. Enabled: service.Autoscaling.Enabled,
  401. MinInstances: int32(service.Autoscaling.MinInstances),
  402. MaxInstances: int32(service.Autoscaling.MaxInstances),
  403. CpuThresholdPercent: int32(service.Autoscaling.CpuThresholdPercent),
  404. MemoryThresholdPercent: int32(service.Autoscaling.MemoryThresholdPercent),
  405. }
  406. }
  407. workerConfig.Autoscaling = autoscaling
  408. var healthCheck *porterv1.HealthCheck
  409. if service.HealthCheck != nil {
  410. healthCheck = &porterv1.HealthCheck{
  411. Enabled: service.HealthCheck.Enabled,
  412. HttpPath: service.HealthCheck.HttpPath,
  413. Command: service.HealthCheck.Command,
  414. TimeoutSeconds: int32(service.HealthCheck.TimeoutSeconds),
  415. InitialDelaySeconds: service.HealthCheck.InitialDelaySeconds,
  416. }
  417. }
  418. workerConfig.HealthCheck = healthCheck
  419. if service.Sleep != nil {
  420. serviceProto.Sleep = service.Sleep
  421. }
  422. serviceProto.Config = &porterv1.Service_WorkerConfig{
  423. WorkerConfig: workerConfig,
  424. }
  425. case porterv1.ServiceType_SERVICE_TYPE_JOB:
  426. jobConfig := &porterv1.JobServiceConfig{
  427. AllowConcurrentOptional: service.AllowConcurrent,
  428. Cron: service.Cron,
  429. }
  430. if service.SuspendCron != nil {
  431. jobConfig.SuspendCron = service.SuspendCron
  432. }
  433. if service.TimeoutSeconds != 0 {
  434. jobConfig.TimeoutSeconds = int64(service.TimeoutSeconds)
  435. }
  436. serviceProto.Config = &porterv1.Service_JobConfig{
  437. JobConfig: jobConfig,
  438. }
  439. }
  440. return serviceProto, nil
  441. }
  442. // AppFromProto converts a PorterApp proto object into a PorterApp struct
  443. func AppFromProto(appProto *porterv1.PorterApp) (PorterApp, error) {
  444. porterApp := PorterApp{
  445. Version: "v2",
  446. Name: appProto.Name,
  447. }
  448. if appProto.Build != nil {
  449. porterApp.Build = &Build{
  450. Context: appProto.Build.Context,
  451. Method: appProto.Build.Method,
  452. Builder: appProto.Build.Builder,
  453. Buildpacks: appProto.Build.Buildpacks,
  454. Dockerfile: appProto.Build.Dockerfile,
  455. CommitSHA: appProto.Build.CommitSha,
  456. }
  457. }
  458. if appProto.Image != nil {
  459. porterApp.Image = &Image{
  460. Repository: appProto.Image.Repository,
  461. Tag: appProto.Image.Tag,
  462. }
  463. }
  464. uniqueServices := uniqueServices(appProto.Services, appProto.ServiceList) // nolint:staticcheck // temporarily using deprecated field for backwards compatibility
  465. for _, service := range uniqueServices {
  466. appService, err := appServiceFromProto(service)
  467. if err != nil {
  468. return porterApp, err
  469. }
  470. porterApp.Services = append(porterApp.Services, appService)
  471. }
  472. if appProto.Predeploy != nil {
  473. appPredeploy, err := appServiceFromProto(appProto.Predeploy)
  474. if err != nil {
  475. return porterApp, err
  476. }
  477. porterApp.Predeploy = &appPredeploy
  478. }
  479. if appProto.InitialDeploy != nil {
  480. appInitialDeploy, err := appServiceFromProto(appProto.InitialDeploy)
  481. if err != nil {
  482. return porterApp, err
  483. }
  484. porterApp.InitialDeploy = &appInitialDeploy
  485. }
  486. for _, envGroup := range appProto.EnvGroups {
  487. if envGroup != nil {
  488. porterApp.EnvGroups = append(porterApp.EnvGroups, fmt.Sprintf("%s:v%d", envGroup.Name, envGroup.Version))
  489. }
  490. }
  491. if appProto.EfsStorage != nil {
  492. porterApp.EfsStorage = &EfsStorage{
  493. Enabled: appProto.EfsStorage.Enabled,
  494. }
  495. }
  496. if appProto.AutoRollback != nil {
  497. porterApp.AutoRollback = &AutoRollback{
  498. Enabled: appProto.AutoRollback.Enabled,
  499. }
  500. }
  501. return porterApp, nil
  502. }
  503. func appServiceFromProto(service *porterv1.Service) (Service, error) {
  504. var gpu *GPU
  505. if service.Gpu != nil {
  506. gpu = &GPU{
  507. Enabled: service.Gpu.Enabled,
  508. GpuCoresNvidia: int(service.Gpu.GpuCoresNvidia),
  509. }
  510. }
  511. appService := Service{
  512. Name: service.Name,
  513. Run: service.RunOptional,
  514. Instances: service.InstancesOptional,
  515. CpuCores: service.CpuCores,
  516. RamMegabytes: int(service.RamMegabytes),
  517. GpuCoresNvidia: service.GpuCoresNvidia, // nolint:staticcheck // https://linear.app/porter/issue/POR-2137/support-new-gpu-field-in-porteryaml
  518. Port: int(service.Port),
  519. SmartOptimization: service.SmartOptimization,
  520. GPU: gpu,
  521. TerminationGracePeriodSeconds: service.TerminationGracePeriodSeconds,
  522. Sleep: service.Sleep,
  523. }
  524. switch service.Type {
  525. default:
  526. return appService, fmt.Errorf("invalid service type '%s'", service.Type)
  527. case porterv1.ServiceType_SERVICE_TYPE_UNSPECIFIED:
  528. return appService, errors.New("Service type unspecified")
  529. case porterv1.ServiceType_SERVICE_TYPE_WEB:
  530. webConfig := service.GetWebConfig()
  531. appService.Type = "web"
  532. var autoscaling *AutoScaling
  533. if webConfig.Autoscaling != nil {
  534. autoscaling = &AutoScaling{
  535. Enabled: webConfig.Autoscaling.Enabled,
  536. MinInstances: int(webConfig.Autoscaling.MinInstances),
  537. MaxInstances: int(webConfig.Autoscaling.MaxInstances),
  538. CpuThresholdPercent: int(webConfig.Autoscaling.CpuThresholdPercent),
  539. MemoryThresholdPercent: int(webConfig.Autoscaling.MemoryThresholdPercent),
  540. }
  541. }
  542. appService.Autoscaling = autoscaling
  543. var healthCheck *HealthCheck
  544. if webConfig.HealthCheck != nil {
  545. healthCheck = &HealthCheck{
  546. Enabled: webConfig.HealthCheck.Enabled,
  547. HttpPath: webConfig.HealthCheck.HttpPath,
  548. Command: webConfig.HealthCheck.Command,
  549. TimeoutSeconds: int(webConfig.HealthCheck.TimeoutSeconds),
  550. InitialDelaySeconds: webConfig.HealthCheck.InitialDelaySeconds,
  551. }
  552. }
  553. appService.HealthCheck = healthCheck
  554. domains := make([]Domains, 0)
  555. for _, domain := range webConfig.Domains {
  556. domains = append(domains, Domains{
  557. Name: domain.Name,
  558. })
  559. }
  560. appService.Domains = domains
  561. appService.IngressAnnotations = webConfig.IngressAnnotations
  562. if webConfig.Private != nil {
  563. appService.Private = webConfig.Private
  564. }
  565. if webConfig.DisableTls != nil {
  566. appService.DisableTLS = webConfig.DisableTls
  567. }
  568. case porterv1.ServiceType_SERVICE_TYPE_WORKER:
  569. workerConfig := service.GetWorkerConfig()
  570. appService.Type = "worker"
  571. var autoscaling *AutoScaling
  572. if workerConfig.Autoscaling != nil {
  573. autoscaling = &AutoScaling{
  574. Enabled: workerConfig.Autoscaling.Enabled,
  575. MinInstances: int(workerConfig.Autoscaling.MinInstances),
  576. MaxInstances: int(workerConfig.Autoscaling.MaxInstances),
  577. CpuThresholdPercent: int(workerConfig.Autoscaling.CpuThresholdPercent),
  578. MemoryThresholdPercent: int(workerConfig.Autoscaling.MemoryThresholdPercent),
  579. }
  580. }
  581. appService.Autoscaling = autoscaling
  582. var healthCheck *HealthCheck
  583. if workerConfig.HealthCheck != nil {
  584. healthCheck = &HealthCheck{
  585. Enabled: workerConfig.HealthCheck.Enabled,
  586. HttpPath: workerConfig.HealthCheck.HttpPath,
  587. Command: workerConfig.HealthCheck.Command,
  588. TimeoutSeconds: int(workerConfig.HealthCheck.TimeoutSeconds),
  589. InitialDelaySeconds: workerConfig.HealthCheck.InitialDelaySeconds,
  590. }
  591. }
  592. appService.HealthCheck = healthCheck
  593. case porterv1.ServiceType_SERVICE_TYPE_JOB:
  594. jobConfig := service.GetJobConfig()
  595. appService.Type = "job"
  596. appService.AllowConcurrent = jobConfig.AllowConcurrentOptional
  597. appService.Cron = jobConfig.Cron
  598. appService.SuspendCron = jobConfig.SuspendCron
  599. appService.TimeoutSeconds = int(jobConfig.TimeoutSeconds)
  600. }
  601. return appService, nil
  602. }
  603. func uniqueServices(serviceMap map[string]*porterv1.Service, serviceList []*porterv1.Service) []*porterv1.Service {
  604. if serviceList != nil {
  605. return serviceList
  606. }
  607. // deduplicate services by name, favoring whatever was defined first
  608. uniqueServices := make(map[string]*porterv1.Service)
  609. for name, service := range serviceMap {
  610. service.Name = name
  611. uniqueServices[service.Name] = service
  612. }
  613. mergedServiceList := make([]*porterv1.Service, 0)
  614. for _, service := range uniqueServices {
  615. mergedServiceList = append(mergedServiceList, service)
  616. }
  617. return mergedServiceList
  618. }