| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- package v2
- import (
- "context"
- "errors"
- "fmt"
- "strings"
- "github.com/ghodss/yaml"
- porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
- "github.com/porter-dev/porter/internal/telemetry"
- )
- // AppProtoFromYaml converts a Porter YAML file into a PorterApp proto object
- func AppProtoFromYaml(ctx context.Context, porterYamlBytes []byte) (*porterv1.PorterApp, error) {
- ctx, span := telemetry.NewSpan(ctx, "v2-app-proto-from-yaml")
- defer span.End()
- if porterYamlBytes == nil {
- return nil, telemetry.Error(ctx, span, nil, "porter yaml is nil")
- }
- porterYaml := &PorterYAML{}
- err := yaml.Unmarshal(porterYamlBytes, porterYaml)
- if err != nil {
- return nil, telemetry.Error(ctx, span, err, "error unmarshaling porter yaml")
- }
- appProto := &porterv1.PorterApp{
- Name: porterYaml.Name,
- Env: porterYaml.Env,
- }
- if porterYaml.Build != nil {
- appProto.Build = &porterv1.Build{
- Context: porterYaml.Build.Context,
- Method: porterYaml.Build.Method,
- Builder: porterYaml.Build.Builder,
- Buildpacks: porterYaml.Build.Buildpacks,
- Dockerfile: porterYaml.Build.Dockerfile,
- }
- }
- if porterYaml.Image != nil {
- appProto.Image = &porterv1.AppImage{
- Repository: porterYaml.Image.Repository,
- Tag: porterYaml.Image.Tag,
- }
- }
- if porterYaml.Services == nil {
- return nil, telemetry.Error(ctx, span, nil, "porter yaml is missing services")
- }
- services := make(map[string]*porterv1.Service, 0)
- for name, service := range porterYaml.Services {
- serviceType, err := protoEnumFromType(name, service)
- if err != nil {
- return nil, telemetry.Error(ctx, span, err, "error getting service type")
- }
- serviceProto, err := serviceProtoFromConfig(service, serviceType)
- if err != nil {
- return nil, telemetry.Error(ctx, span, err, "error casting service config")
- }
- services[name] = serviceProto
- }
- appProto.Services = services
- if porterYaml.Predeploy != nil {
- predeployProto, err := serviceProtoFromConfig(*porterYaml.Predeploy, porterv1.ServiceType_SERVICE_TYPE_JOB)
- if err != nil {
- return nil, telemetry.Error(ctx, span, err, "error casting predeploy config")
- }
- appProto.Predeploy = predeployProto
- }
- return appProto, nil
- }
- // PorterYAML represents all the possible fields in a Porter YAML file
- type PorterYAML struct {
- Name string `yaml:"name"`
- Services map[string]Service `yaml:"services"`
- Image *Image `yaml:"image"`
- Build *Build `yaml:"build"`
- Env map[string]string `yaml:"env"`
- Predeploy *Service `yaml:"predeploy"`
- }
- // Build represents the build settings for a Porter app
- 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"`
- }
- // Service represents a single service in a porter app
- type Service struct {
- Run string `yaml:"run"`
- Type string `yaml:"type" validate:"required, oneof=web worker job"`
- Instances int `yaml:"instances"`
- CpuCores float32 `yaml:"cpuCores"`
- RamMegabytes int `yaml:"ramMegabytes"`
- Port int `yaml:"port"`
- Autoscaling *AutoScaling `yaml:"autoscaling,omitempty" validate:"excluded_if=Type job"`
- Domains []Domains `yaml:"domains" validate:"excluded_unless=Type web"`
- HealthCheck *HealthCheck `yaml:"healthCheck,omitempty" validate:"excluded_unless=Type web"`
- AllowConcurrent bool `yaml:"allowConcurrent" validate:"excluded_unless=Type job"`
- Cron string `yaml:"cron" validate:"excluded_unless=Type job"`
- Private bool `yaml:"private" validate:"excluded_unless=Type web"`
- }
- // AutoScaling represents the autoscaling settings for web services
- type AutoScaling struct {
- Enabled bool `yaml:"enabled"`
- MinInstances int `yaml:"minInstances"`
- MaxInstances int `yaml:"maxInstances"`
- CpuThresholdPercent int `yaml:"cpuThresholdPercent"`
- MemoryThresholdPercent int `yaml:"memoryThresholdPercent"`
- }
- // Domains are the custom domains for a web service
- type Domains struct {
- Name string `yaml:"name"`
- }
- // HealthCheck is the health check settings for a web service
- type HealthCheck struct {
- Enabled bool `yaml:"enabled"`
- HttpPath string `yaml:"httpPath"`
- }
- // Image is the repository and tag for an app's build image
- type Image struct {
- Repository string `yaml:"repository"`
- Tag string `yaml:"tag"`
- }
- func protoEnumFromType(name string, service Service) (porterv1.ServiceType, error) {
- var serviceType porterv1.ServiceType
- if service.Type != "" {
- if service.Type == "web" {
- return porterv1.ServiceType_SERVICE_TYPE_WEB, nil
- }
- if service.Type == "worker" {
- return porterv1.ServiceType_SERVICE_TYPE_WORKER, nil
- }
- if service.Type == "job" {
- return porterv1.ServiceType_SERVICE_TYPE_JOB, nil
- }
- return serviceType, fmt.Errorf("invalid service type '%s'", service.Type)
- }
- if strings.Contains(name, "web") {
- return porterv1.ServiceType_SERVICE_TYPE_WEB, nil
- }
- if strings.Contains(name, "wkr") {
- return porterv1.ServiceType_SERVICE_TYPE_WORKER, nil
- }
- if strings.Contains(name, "job") {
- return porterv1.ServiceType_SERVICE_TYPE_JOB, nil
- }
- 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) {
- serviceProto := &porterv1.Service{
- Run: service.Run,
- Type: serviceType,
- Instances: int32(service.Instances),
- CpuCores: service.CpuCores,
- RamMegabytes: int32(service.RamMegabytes),
- Port: int32(service.Port),
- }
- switch serviceType {
- default:
- return nil, fmt.Errorf("invalid service type '%s'", serviceType)
- case porterv1.ServiceType_SERVICE_TYPE_UNSPECIFIED:
- return nil, errors.New("Service type unspecified")
- case porterv1.ServiceType_SERVICE_TYPE_WEB:
- webConfig := &porterv1.WebServiceConfig{}
- var autoscaling *porterv1.Autoscaling
- if service.Autoscaling != nil {
- autoscaling = &porterv1.Autoscaling{
- Enabled: service.Autoscaling.Enabled,
- MinInstances: int32(service.Autoscaling.MinInstances),
- MaxInstances: int32(service.Autoscaling.MaxInstances),
- CpuThresholdPercent: int32(service.Autoscaling.CpuThresholdPercent),
- MemoryThresholdPercent: int32(service.Autoscaling.MemoryThresholdPercent),
- }
- }
- webConfig.Autoscaling = autoscaling
- var healthCheck *porterv1.HealthCheck
- if service.HealthCheck != nil {
- healthCheck = &porterv1.HealthCheck{
- Enabled: service.HealthCheck.Enabled,
- HttpPath: service.HealthCheck.HttpPath,
- }
- }
- webConfig.HealthCheck = healthCheck
- domains := make([]*porterv1.Domain, 0)
- for _, domain := range service.Domains {
- domains = append(domains, &porterv1.Domain{
- Name: domain.Name,
- })
- }
- webConfig.Domains = domains
- webConfig.Private = service.Private
- serviceProto.Config = &porterv1.Service_WebConfig{
- WebConfig: webConfig,
- }
- case porterv1.ServiceType_SERVICE_TYPE_WORKER:
- workerConfig := &porterv1.WorkerServiceConfig{}
- var autoscaling *porterv1.Autoscaling
- if service.Autoscaling != nil {
- autoscaling = &porterv1.Autoscaling{
- Enabled: service.Autoscaling.Enabled,
- MinInstances: int32(service.Autoscaling.MinInstances),
- MaxInstances: int32(service.Autoscaling.MaxInstances),
- CpuThresholdPercent: int32(service.Autoscaling.CpuThresholdPercent),
- MemoryThresholdPercent: int32(service.Autoscaling.MemoryThresholdPercent),
- }
- }
- workerConfig.Autoscaling = autoscaling
- serviceProto.Config = &porterv1.Service_WorkerConfig{
- WorkerConfig: workerConfig,
- }
- case porterv1.ServiceType_SERVICE_TYPE_JOB:
- jobConfig := &porterv1.JobServiceConfig{
- AllowConcurrent: service.AllowConcurrent,
- Cron: service.Cron,
- }
- serviceProto.Config = &porterv1.Service_JobConfig{
- JobConfig: jobConfig,
- }
- }
- return serviceProto, nil
- }
|