| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- package porter_app
- import (
- "context"
- "encoding/base64"
- "errors"
- "time"
- "connectrpc.com/connect"
- "github.com/google/uuid"
- "github.com/porter-dev/api-contracts/generated/go/helpers"
- porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
- "github.com/porter-dev/api-contracts/generated/go/porter/v1/porterv1connect"
- "github.com/porter-dev/porter/internal/deployment_target"
- "github.com/porter-dev/porter/internal/kubernetes"
- "github.com/porter-dev/porter/internal/kubernetes/environment_groups"
- "github.com/porter-dev/porter/internal/models"
- "github.com/porter-dev/porter/internal/repository"
- "github.com/porter-dev/porter/internal/telemetry"
- )
- // Revision represents the data for a single revision
- type Revision struct {
- // ID is the revision id
- ID string `json:"id"`
- // B64AppProto is the base64 encoded app proto definition
- B64AppProto string `json:"b64_app_proto"`
- // Status is the status of the revision
- Status models.AppRevisionStatus `json:"status"`
- // RevisionNumber is the revision number with respect to the app and deployment target
- RevisionNumber uint64 `json:"revision_number"`
- // CreatedAt is the time the revision was created
- CreatedAt time.Time `json:"created_at"`
- // UpdatedAt is the time the revision was updated
- UpdatedAt time.Time `json:"updated_at"`
- // DeploymentTargetID is the id of the deployment target the revision is associated with
- DeploymentTarget DeploymentTarget `json:"deployment_target"`
- // Env is the environment variables for the revision
- Env environment_groups.EnvironmentGroup `json:"env,omitempty"`
- // AppInstanceID is the id of the app instance the revision is associated with
- AppInstanceID uuid.UUID `json:"app_instance_id"`
- }
- // AppInstance represents the data for an app instance
- type AppInstance struct {
- // Id is the app instance id
- Id string `json:"id"`
- // DeploymentTargetID is the id of the deployment target the revision is associated with
- DeploymentTarget DeploymentTarget `json:"deployment_target"`
- // Name is the name of the app instance
- Name string `json:"name"`
- }
- // RevisionProgress describes the progress of a revision in its lifecycle
- type RevisionProgress struct {
- // PredeployStarted is true if the predeploy process has started
- PredeployStarted bool `json:"predeploy_started"`
- // PredeploySuccessful is true if the predeploy process has completed successfully
- PredeploySuccessful bool `json:"predeploy_successful"`
- // PredeployFailed is true if the predeploy process has failed
- PredeployFailed bool `json:"predeploy_failed"`
- // InstallStarted is true if the install process has started
- InstallStarted bool `json:"install_started"`
- // InstallSuccessful is true if the install process has completed successfully
- InstallSuccessful bool `json:"install_successful"`
- // InstallFailed is true if the install process has failed
- InstallFailed bool `json:"install_failed"`
- // DeploymentStarted is true if the deployment process has started
- DeploymentStarted bool `json:"deployment_started"`
- // DeploymentSuccessful is true if the deployment process has completed successfully
- DeploymentSuccessful bool `json:"deployment_successful"`
- // DeploymentFailed is true if the deployment process has failed
- DeploymentFailed bool `json:"deployment_failed"`
- // IsInTerminalStatus is true if the revision is in a terminal status
- IsInTerminalStatus bool `json:"is_in_terminal_status"`
- }
- // DeploymentTarget is a simplified version of the deployment target struct
- type DeploymentTarget struct {
- ID string `json:"id"`
- Name string `json:"name"`
- }
- // GetAppRevisionInput is the input struct for GetAppRevisions
- type GetAppRevisionInput struct {
- ProjectID uint
- AppRevisionID uuid.UUID
- CCPClient porterv1connect.ClusterControlPlaneServiceClient
- }
- // GetAppRevision returns a single app revision by id
- func GetAppRevision(ctx context.Context, inp GetAppRevisionInput) (Revision, error) {
- ctx, span := telemetry.NewSpan(ctx, "get-app-revision")
- defer span.End()
- var revision Revision
- if inp.ProjectID == 0 {
- return revision, telemetry.Error(ctx, span, nil, "must provide a project id")
- }
- if inp.AppRevisionID == uuid.Nil {
- return revision, telemetry.Error(ctx, span, nil, "must provide an app revision id")
- }
- getRevisionReq := connect.NewRequest(&porterv1.GetAppRevisionRequest{
- ProjectId: int64(inp.ProjectID),
- AppRevisionId: inp.AppRevisionID.String(),
- })
- ccpResp, err := inp.CCPClient.GetAppRevision(ctx, getRevisionReq)
- if err != nil {
- return revision, telemetry.Error(ctx, span, err, "error getting app revision")
- }
- if ccpResp == nil || ccpResp.Msg == nil {
- return revision, telemetry.Error(ctx, span, nil, "get app revision response is nil")
- }
- appRevisionProto := ccpResp.Msg.AppRevision
- revision, err = EncodedRevisionFromProto(ctx, appRevisionProto)
- if err != nil {
- return revision, telemetry.Error(ctx, span, err, "error converting app revision from proto")
- }
- return revision, nil
- }
- // EncodedRevisionFromProto converts an AppRevision proto object into a Revision object
- func EncodedRevisionFromProto(ctx context.Context, appRevision *porterv1.AppRevision) (Revision, error) {
- ctx, span := telemetry.NewSpan(ctx, "encoded-revision-from-proto")
- defer span.End()
- var revision Revision
- if appRevision == nil {
- return revision, telemetry.Error(ctx, span, nil, "current app revision definition is nil")
- }
- appProto := appRevision.App
- if appProto == nil {
- return revision, telemetry.Error(ctx, span, nil, "app proto is nil")
- }
- encoded, err := helpers.MarshalContractObject(ctx, appProto)
- if err != nil {
- return revision, telemetry.Error(ctx, span, err, "error marshalling app proto back to json")
- }
- b64 := base64.StdEncoding.EncodeToString(encoded)
- status, err := appRevisionStatusFromProto(appRevision.Status)
- telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "status", Value: string(status)})
- if err != nil {
- _ = telemetry.Error(ctx, span, nil, "unknown revision type") // flagged as an error for visibility
- }
- appInstanceIdStr := appRevision.AppInstanceId
- appInstanceId, err := uuid.Parse(appInstanceIdStr)
- if err != nil {
- return revision, telemetry.Error(ctx, span, err, "error parsing app instance id")
- }
- revision = Revision{
- B64AppProto: b64,
- Status: status,
- ID: appRevision.Id,
- RevisionNumber: appRevision.RevisionNumber,
- CreatedAt: appRevision.CreatedAt.AsTime(),
- UpdatedAt: appRevision.UpdatedAt.AsTime(),
- DeploymentTarget: DeploymentTarget{ID: appRevision.DeploymentTargetId},
- AppInstanceID: appInstanceId,
- }
- return revision, nil
- }
- // AttachEnvToRevisionInput is the input struct for AttachEnvToRevision
- type AttachEnvToRevisionInput struct {
- ProjectID uint
- ClusterID int
- Revision Revision
- DeploymentTarget deployment_target.DeploymentTarget
- K8SAgent *kubernetes.Agent
- PorterAppRepository repository.PorterAppRepository
- }
- // AttachEnvToRevision attaches the environment variables from the app's default env group to a revision
- // These are the variables that are displayed to the user in the UI as associated with the app rather than an env group
- func AttachEnvToRevision(ctx context.Context, inp AttachEnvToRevisionInput) (Revision, error) {
- ctx, span := telemetry.NewSpan(ctx, "attach-env-to-revision")
- defer span.End()
- revision := inp.Revision
- if inp.ProjectID == 0 {
- return revision, telemetry.Error(ctx, span, nil, "must provide a project id")
- }
- if inp.ClusterID == 0 {
- return revision, telemetry.Error(ctx, span, nil, "must provide a cluster id")
- }
- if inp.K8SAgent == nil {
- return revision, telemetry.Error(ctx, span, nil, "k8s agent is nil")
- }
- decoded, err := base64.StdEncoding.DecodeString(revision.B64AppProto)
- if err != nil {
- return revision, telemetry.Error(ctx, span, err, "error decoding app proto")
- }
- appDef := &porterv1.PorterApp{}
- err = helpers.UnmarshalContractObject(decoded, appDef)
- if err != nil {
- return revision, telemetry.Error(ctx, span, err, "error unmarshalling app proto")
- }
- envName, err := AppEnvGroupName(ctx, appDef.Name, inp.Revision.DeploymentTarget.ID, uint(inp.ClusterID), inp.PorterAppRepository)
- if err != nil {
- return revision, telemetry.Error(ctx, span, err, "error getting app env group name")
- }
- envNameFilter := []string{envName}
- envFromProtoInp := AppEnvironmentFromProtoInput{
- ProjectID: inp.ProjectID,
- ClusterID: inp.ClusterID,
- App: appDef,
- K8SAgent: inp.K8SAgent,
- DeploymentTarget: inp.DeploymentTarget,
- }
- envGroups, err := AppEnvironmentFromProto(ctx, envFromProtoInp, WithEnvGroupFilter(envNameFilter), WithSecrets())
- if err != nil {
- return revision, telemetry.Error(ctx, span, err, "error getting app environment from revision")
- }
- if len(envGroups) > 1 {
- return revision, telemetry.Error(ctx, span, err, "multiple app envs groups returned for same name")
- }
- if len(envGroups) == 1 {
- revision.Env = envGroups[0]
- }
- return revision, nil
- }
- func appRevisionStatusFromProto(status string) (models.AppRevisionStatus, error) {
- appRevisionStatus := models.AppRevisionStatus_Unknown
- switch status {
- case string(models.AppRevisionStatus_ImageAvailable):
- appRevisionStatus = models.AppRevisionStatus_ImageAvailable
- case string(models.AppRevisionStatus_AwaitingBuild):
- appRevisionStatus = models.AppRevisionStatus_AwaitingBuild
- case string(models.AppRevisionStatus_AwaitingPredeploy):
- appRevisionStatus = models.AppRevisionStatus_AwaitingPredeploy
- case string(models.AppRevisionStatus_InstallSuccessful):
- appRevisionStatus = models.AppRevisionStatus_InstallSuccessful
- case string(models.AppRevisionStatus_InstallProgressing):
- appRevisionStatus = models.AppRevisionStatus_InstallProgressing
- case string(models.AppRevisionStatus_AwaitingInstall):
- appRevisionStatus = models.AppRevisionStatus_AwaitingInstall
- case string(models.AppRevisionStatus_BuildCanceled):
- appRevisionStatus = models.AppRevisionStatus_BuildCanceled
- case string(models.AppRevisionStatus_BuildFailed):
- appRevisionStatus = models.AppRevisionStatus_BuildFailed
- case string(models.AppRevisionStatus_PredeployFailed):
- appRevisionStatus = models.AppRevisionStatus_PredeployFailed
- case string(models.AppRevisionStatus_PredeploySuccessful):
- appRevisionStatus = models.AppRevisionStatus_PredeploySuccessful
- case string(models.AppRevisionStatus_PredeployProgressing):
- appRevisionStatus = models.AppRevisionStatus_PredeployProgressing
- case string(models.AppRevisionStatus_InstallFailed):
- appRevisionStatus = models.AppRevisionStatus_InstallFailed
- case string(models.AppRevisionStatus_Created):
- appRevisionStatus = models.AppRevisionStatus_Created
- case string(models.AppRevisionStatus_BuildSuccessful):
- appRevisionStatus = models.AppRevisionStatus_BuildSuccessful
- case string(models.AppRevisionStatus_ApplyFailed):
- appRevisionStatus = models.AppRevisionStatus_ApplyFailed
- case string(models.AppRevisionStatus_UpdateFailed):
- appRevisionStatus = models.AppRevisionStatus_UpdateFailed
- case string(models.AppRevisionStatus_DeploymentProgressing):
- appRevisionStatus = models.AppRevisionStatus_DeploymentProgressing
- case string(models.AppRevisionStatus_DeploymentSuccessful):
- appRevisionStatus = models.AppRevisionStatus_DeploymentSuccessful
- case string(models.AppRevisionStatus_DeploymentFailed):
- appRevisionStatus = models.AppRevisionStatus_DeploymentFailed
- case string(models.AppRevisionStatus_DeploymentSuperseded):
- appRevisionStatus = models.AppRevisionStatus_DeploymentSuperseded
- case string(models.AppRevisionStatus_RollbackSuccessful):
- appRevisionStatus = models.AppRevisionStatus_RollbackSuccessful
- case string(models.AppRevisionStatus_RollbackFailed):
- appRevisionStatus = models.AppRevisionStatus_RollbackFailed
- default:
- return appRevisionStatus, errors.New("unknown app revision status")
- }
- return appRevisionStatus, nil
- }
|