Procházet zdrojové kódy

POR-1441 parse addons from porter.yaml (#4022)

ianedwards před 2 roky
rodič
revize
a36def3588

+ 38 - 1
api/server/handlers/porter_app/parse_yaml.go

@@ -47,6 +47,7 @@ type EncodedAppWithEnv struct {
 	B64AppProto  string            `json:"b64_app_proto"`
 	EnvVariables map[string]string `json:"env_variables"`
 	EnvSecrets   map[string]string `json:"env_secrets"`
+	B64Addons    []string          `json:"b64_addons"`
 }
 
 // ParsePorterYAMLToProtoResponse is the response object for the /apps/parse endpoint
@@ -117,6 +118,14 @@ func (c *ParsePorterYAMLToProtoHandler) ServeHTTP(w http.ResponseWriter, r *http
 	response.B64AppProto = encodedApp
 	response.EnvVariables = appDefinition.EnvVariables
 
+	encodedAddons, err := encodeAddonProtos(ctx, appDefinition.Addons)
+	if err != nil {
+		err := telemetry.Error(ctx, span, err, "error encoding addon protos")
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+		return
+	}
+	response.B64Addons = encodedAddons
+
 	if appDefinition.PreviewApp != nil {
 		encodedPreviewApp, err := encodeAppProto(ctx, appDefinition.PreviewApp.AppProto)
 		if err != nil {
@@ -124,9 +133,18 @@ func (c *ParsePorterYAMLToProtoHandler) ServeHTTP(w http.ResponseWriter, r *http
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
 			return
 		}
+
+		encodedPreviewAddons, err := encodeAddonProtos(ctx, appDefinition.PreviewApp.Addons)
+		if err != nil {
+			err := telemetry.Error(ctx, span, err, "error encoding preview addon protos")
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
+			return
+		}
+
 		response.PreviewApp = &EncodedAppWithEnv{
 			B64AppProto:  encodedPreviewApp,
 			EnvVariables: appDefinition.PreviewApp.EnvVariables,
+			B64Addons:    encodedPreviewAddons,
 		}
 		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "includes-preview-app", Value: true})
 	}
@@ -142,10 +160,29 @@ func encodeAppProto(ctx context.Context, app *porterv1.PorterApp) (string, error
 
 	by, err := helpers.MarshalContractObject(ctx, app)
 	if err != nil {
-		return encodedApp, err
+		return encodedApp, telemetry.Error(ctx, span, err, "error marshaling app proto")
 	}
 
 	encodedApp = base64.StdEncoding.EncodeToString(by)
 
 	return encodedApp, nil
 }
+
+func encodeAddonProtos(ctx context.Context, addons []*porterv1.Addon) ([]string, error) {
+	ctx, span := telemetry.NewSpan(ctx, "encode-addon-proto")
+	defer span.End()
+
+	var encodedAddons []string
+
+	for _, addon := range addons {
+		by, err := helpers.MarshalContractObject(ctx, addon)
+		if err != nil {
+			return encodedAddons, telemetry.Error(ctx, span, err, "error marshaling addon proto")
+		}
+
+		encodedAddon := base64.StdEncoding.EncodeToString(by)
+		encodedAddons = append(encodedAddons, encodedAddon)
+	}
+
+	return encodedAddons, nil
+}

+ 1 - 1
go.mod

@@ -83,7 +83,7 @@ require (
 	github.com/matryer/is v1.4.0
 	github.com/nats-io/nats.go v1.24.0
 	github.com/open-policy-agent/opa v0.44.0
-	github.com/porter-dev/api-contracts v0.2.54
+	github.com/porter-dev/api-contracts v0.2.55
 	github.com/riandyrn/otelchi v0.5.1
 	github.com/santhosh-tekuri/jsonschema/v5 v5.0.1
 	github.com/stefanmcshane/helm v0.0.0-20221213002717-88a4a2c6e77d

+ 2 - 2
go.sum

@@ -1520,8 +1520,8 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
-github.com/porter-dev/api-contracts v0.2.54 h1:EgA0iACycQhsQ3jahjKr5/niNLGC0s6QEHWb9vYYXs0=
-github.com/porter-dev/api-contracts v0.2.54/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
+github.com/porter-dev/api-contracts v0.2.55 h1:H8RvD004mX4uWrlRVcL8kzo7ZtFQyZDN+X9j+2bW7fc=
+github.com/porter-dev/api-contracts v0.2.55/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
 github.com/porter-dev/switchboard v0.0.3 h1:dBuYkiVLa5Ce7059d6qTe9a1C2XEORFEanhbtV92R+M=
 github.com/porter-dev/switchboard v0.0.3/go.mod h1:xSPzqSFMQ6OSbp42fhCi4AbGbQbsm6nRvOkrblFeXU4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=

+ 2 - 0
go.work.sum

@@ -839,6 +839,8 @@ github.com/porter-dev/api-contracts v0.2.27 h1:NZTWmbiqQF082Kl0vUtXev5gcI8lTY6bo
 github.com/porter-dev/api-contracts v0.2.27/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
 github.com/porter-dev/api-contracts v0.2.35 h1:BDxOMKQrYvh/3qsSiUYWM+btBx4+oVjA2mH4+0C/sHY=
 github.com/porter-dev/api-contracts v0.2.35/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
+github.com/porter-dev/api-contracts v0.2.55 h1:H8RvD004mX4uWrlRVcL8kzo7ZtFQyZDN+X9j+2bW7fc=
+github.com/porter-dev/api-contracts v0.2.55/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
 github.com/porter-dev/switchboard v0.0.3 h1:dBuYkiVLa5Ce7059d6qTe9a1C2XEORFEanhbtV92R+M=
 github.com/porter-dev/switchboard v0.0.3/go.mod h1:xSPzqSFMQ6OSbp42fhCi4AbGbQbsm6nRvOkrblFeXU4=
 github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=

+ 72 - 0
internal/porter_app/v2/addons.go

@@ -0,0 +1,72 @@
+package v2
+
+import (
+	"context"
+
+	porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
+	"github.com/porter-dev/porter/internal/telemetry"
+)
+
+// ProtoFromAddon converts an Addon to the Addon proto type
+func ProtoFromAddon(ctx context.Context, addon Addon) (*porterv1.Addon, error) {
+	ctx, span := telemetry.NewSpan(ctx, "proto-from-addon")
+	defer span.End()
+
+	addonProto := &porterv1.Addon{
+		Name: addon.Name,
+	}
+
+	addonType, err := addonEnumProtoFromType(ctx, addon.Type)
+	if err != nil {
+		return addonProto, telemetry.Error(ctx, span, err, "error getting addon type")
+	}
+
+	switch addonType {
+	case porterv1.AddonType_ADDON_TYPE_POSTGRES:
+		addonProto.Type = addonType
+		postgres := postgresConfigProtoFromAddon(addon)
+
+		addonProto.Config = &porterv1.Addon_Postgres{
+			Postgres: postgres,
+		}
+	default:
+		return addonProto, telemetry.Error(ctx, span, nil, "specified addon type not supported")
+	}
+
+	var envGroups []*porterv1.EnvGroup
+
+	for _, envGroup := range addon.EnvGroups {
+		eg := &porterv1.EnvGroup{
+			Name:    envGroup.Name,
+			Version: int64(envGroup.Version),
+		}
+		envGroups = append(envGroups, eg)
+	}
+	addonProto.EnvGroups = envGroups
+
+	return addonProto, nil
+}
+
+func addonEnumProtoFromType(ctx context.Context, addonType string) (porterv1.AddonType, error) {
+	ctx, span := telemetry.NewSpan(ctx, "addon-enum-proto-from-type")
+	defer span.End()
+
+	var addonTypeEnum porterv1.AddonType
+
+	switch addonType {
+	case "postgres":
+		addonTypeEnum = porterv1.AddonType_ADDON_TYPE_POSTGRES
+	default:
+		return addonTypeEnum, telemetry.Error(ctx, span, nil, "invalid addon type")
+	}
+
+	return addonTypeEnum, nil
+}
+
+func postgresConfigProtoFromAddon(addon Addon) *porterv1.Postgres {
+	return &porterv1.Postgres{
+		RamMegabytes:     int32(addon.RamMegabytes),
+		CpuCores:         addon.CpuCores,
+		StorageGigabytes: int32(addon.StorageGigabytes),
+	}
+}

+ 42 - 3
internal/porter_app/v2/yaml.go

@@ -14,6 +14,7 @@ import (
 // AppProtoWithEnv is a struct containing a PorterApp proto object and its environment variables
 type AppProtoWithEnv struct {
 	AppProto     *porterv1.PorterApp
+	Addons       []*porterv1.Addon
 	EnvVariables map[string]string
 }
 
@@ -47,8 +48,20 @@ func AppProtoFromYaml(ctx context.Context, porterYamlBytes []byte) (AppWithPrevi
 	out.AppProto = appProto
 	out.EnvVariables = envVariables
 
+	var addons []*porterv1.Addon
+	for _, addon := range porterYaml.Addons {
+		addonProto, err := ProtoFromAddon(ctx, addon)
+		if err != nil {
+			return out, telemetry.Error(ctx, span, err, "error converting addon to proto")
+		}
+		addons = append(addons, addonProto)
+	}
+	out.Addons = addons
+
 	if porterYaml.Previews != nil {
-		previewAppProto, previewEnvVariables, err := ProtoFromApp(ctx, *porterYaml.Previews)
+		previewConfig := *porterYaml.Previews
+
+		previewAppProto, previewEnvVariables, err := ProtoFromApp(ctx, previewConfig.PorterApp)
 		if err != nil {
 			return out, telemetry.Error(ctx, span, err, "error converting preview porter yaml to proto")
 		}
@@ -56,6 +69,16 @@ func AppProtoFromYaml(ctx context.Context, porterYamlBytes []byte) (AppWithPrevi
 			AppProto:     previewAppProto,
 			EnvVariables: previewEnvVariables,
 		}
+
+		var previewAddons []*porterv1.Addon
+		for _, addon := range previewConfig.Addons {
+			addonProto, err := ProtoFromAddon(ctx, addon)
+			if err != nil {
+				return out, telemetry.Error(ctx, span, err, "error converting preview addon to proto")
+			}
+			previewAddons = append(previewAddons, addonProto)
+		}
+		out.PreviewApp.Addons = previewAddons
 	}
 
 	return out, nil
@@ -94,10 +117,26 @@ type PorterApp struct {
 	RequiredApps []RequiredApp `yaml:"requiredApps,omitempty"`
 }
 
+// PorterAppWithAddons is the definition of a porter app in a Porter YAML file with addons
+type PorterAppWithAddons struct {
+	PorterApp `yaml:",inline"`
+	Addons    []Addon `yaml:"addons,omitempty"`
+}
+
 // PorterYAML represents all the possible fields in a Porter YAML file
 type PorterYAML struct {
-	PorterApp `yaml:",inline"`
-	Previews  *PorterApp `yaml:"previews,omitempty"`
+	PorterAppWithAddons `yaml:",inline"`
+	Previews            *PorterAppWithAddons `yaml:"previews,omitempty"`
+}
+
+// Addon represents an addon that should be installed alongside a Porter app
+type Addon struct {
+	Name             string     `yaml:"name"`
+	Type             string     `yaml:"type"`
+	EnvGroups        []EnvGroup `yaml:"envGroups,omitempty"`
+	CpuCores         float32    `yaml:"cpuCores,omitempty"`
+	RamMegabytes     int        `yaml:"ramMegabytes,omitempty"`
+	StorageGigabytes float32    `yaml:"storageGigabytes,omitempty"`
 }
 
 // RequiredApp specifies another porter app that this app expects to be deployed alongside it