revisions.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package porter_app
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "time"
  6. "connectrpc.com/connect"
  7. "github.com/google/uuid"
  8. "github.com/porter-dev/api-contracts/generated/go/helpers"
  9. porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
  10. "github.com/porter-dev/api-contracts/generated/go/porter/v1/porterv1connect"
  11. "github.com/porter-dev/porter/internal/deployment_target"
  12. "github.com/porter-dev/porter/internal/kubernetes"
  13. "github.com/porter-dev/porter/internal/kubernetes/environment_groups"
  14. "github.com/porter-dev/porter/internal/repository"
  15. "github.com/porter-dev/porter/internal/telemetry"
  16. )
  17. // Revision represents the data for a single revision
  18. type Revision struct {
  19. // ID is the revision id
  20. ID string `json:"id"`
  21. // B64AppProto is the base64 encoded app proto definition
  22. B64AppProto string `json:"b64_app_proto"`
  23. // Status is the status of the revision
  24. Status string `json:"status"`
  25. // RevisionNumber is the revision number with respect to the app and deployment target
  26. RevisionNumber uint64 `json:"revision_number"`
  27. // CreatedAt is the time the revision was created
  28. CreatedAt time.Time `json:"created_at"`
  29. // UpdatedAt is the time the revision was updated
  30. UpdatedAt time.Time `json:"updated_at"`
  31. // DeploymentTargetID is the id of the deployment target the revision is associated with
  32. DeploymentTargetID string `json:"deployment_target_id"`
  33. // Env is the environment variables for the revision
  34. Env environment_groups.EnvironmentGroup `json:"env,omitempty"`
  35. }
  36. // GetAppRevisionInput is the input struct for GetAppRevisions
  37. type GetAppRevisionInput struct {
  38. ProjectID uint
  39. AppRevisionID uuid.UUID
  40. CCPClient porterv1connect.ClusterControlPlaneServiceClient
  41. }
  42. // GetAppRevision returns a single app revision by id
  43. func GetAppRevision(ctx context.Context, inp GetAppRevisionInput) (Revision, error) {
  44. ctx, span := telemetry.NewSpan(ctx, "get-app-revision")
  45. defer span.End()
  46. var revision Revision
  47. if inp.ProjectID == 0 {
  48. return revision, telemetry.Error(ctx, span, nil, "must provide a project id")
  49. }
  50. if inp.AppRevisionID == uuid.Nil {
  51. return revision, telemetry.Error(ctx, span, nil, "must provide an app revision id")
  52. }
  53. getRevisionReq := connect.NewRequest(&porterv1.GetAppRevisionRequest{
  54. ProjectId: int64(inp.ProjectID),
  55. AppRevisionId: inp.AppRevisionID.String(),
  56. })
  57. ccpResp, err := inp.CCPClient.GetAppRevision(ctx, getRevisionReq)
  58. if err != nil {
  59. return revision, telemetry.Error(ctx, span, err, "error getting app revision")
  60. }
  61. if ccpResp == nil || ccpResp.Msg == nil {
  62. return revision, telemetry.Error(ctx, span, nil, "get app revision response is nil")
  63. }
  64. appRevisionProto := ccpResp.Msg.AppRevision
  65. revision, err = EncodedRevisionFromProto(ctx, appRevisionProto)
  66. if err != nil {
  67. return revision, telemetry.Error(ctx, span, err, "error converting app revision from proto")
  68. }
  69. return revision, nil
  70. }
  71. // EncodedRevisionFromProto converts an AppRevision proto object into a Revision object
  72. func EncodedRevisionFromProto(ctx context.Context, appRevision *porterv1.AppRevision) (Revision, error) {
  73. ctx, span := telemetry.NewSpan(ctx, "encoded-revision-from-proto")
  74. defer span.End()
  75. var revision Revision
  76. if appRevision == nil {
  77. return revision, telemetry.Error(ctx, span, nil, "current app revision definition is nil")
  78. }
  79. appProto := appRevision.App
  80. if appProto == nil {
  81. return revision, telemetry.Error(ctx, span, nil, "app proto is nil")
  82. }
  83. encoded, err := helpers.MarshalContractObject(ctx, appProto)
  84. if err != nil {
  85. return revision, telemetry.Error(ctx, span, err, "error marshalling app proto back to json")
  86. }
  87. b64 := base64.StdEncoding.EncodeToString(encoded)
  88. revision = Revision{
  89. B64AppProto: b64,
  90. Status: appRevision.Status,
  91. ID: appRevision.Id,
  92. RevisionNumber: appRevision.RevisionNumber,
  93. CreatedAt: appRevision.CreatedAt.AsTime(),
  94. UpdatedAt: appRevision.UpdatedAt.AsTime(),
  95. DeploymentTargetID: appRevision.DeploymentTargetId,
  96. }
  97. return revision, nil
  98. }
  99. // AttachEnvToRevisionInput is the input struct for AttachEnvToRevision
  100. type AttachEnvToRevisionInput struct {
  101. ProjectID uint
  102. ClusterID int
  103. Revision Revision
  104. DeploymentTarget deployment_target.DeploymentTarget
  105. K8SAgent *kubernetes.Agent
  106. PorterAppRepository repository.PorterAppRepository
  107. }
  108. // AttachEnvToRevision attaches the environment variables from the app's default env group to a revision
  109. // These are the variables that are displayed to the user in the UI as associated with the app rather than an env group
  110. func AttachEnvToRevision(ctx context.Context, inp AttachEnvToRevisionInput) (Revision, error) {
  111. ctx, span := telemetry.NewSpan(ctx, "attach-env-to-revision")
  112. defer span.End()
  113. revision := inp.Revision
  114. if inp.ProjectID == 0 {
  115. return revision, telemetry.Error(ctx, span, nil, "must provide a project id")
  116. }
  117. if inp.ClusterID == 0 {
  118. return revision, telemetry.Error(ctx, span, nil, "must provide a cluster id")
  119. }
  120. if inp.K8SAgent == nil {
  121. return revision, telemetry.Error(ctx, span, nil, "k8s agent is nil")
  122. }
  123. decoded, err := base64.StdEncoding.DecodeString(revision.B64AppProto)
  124. if err != nil {
  125. return revision, telemetry.Error(ctx, span, err, "error decoding app proto")
  126. }
  127. appDef := &porterv1.PorterApp{}
  128. err = helpers.UnmarshalContractObject(decoded, appDef)
  129. if err != nil {
  130. return revision, telemetry.Error(ctx, span, err, "error unmarshalling app proto")
  131. }
  132. envName, err := AppEnvGroupName(ctx, appDef.Name, inp.Revision.DeploymentTargetID, uint(inp.ClusterID), inp.PorterAppRepository)
  133. if err != nil {
  134. return revision, telemetry.Error(ctx, span, err, "error getting app env group name")
  135. }
  136. envNameFilter := []string{envName}
  137. envFromProtoInp := AppEnvironmentFromProtoInput{
  138. ProjectID: inp.ProjectID,
  139. ClusterID: inp.ClusterID,
  140. App: appDef,
  141. K8SAgent: inp.K8SAgent,
  142. DeploymentTarget: inp.DeploymentTarget,
  143. }
  144. envGroups, err := AppEnvironmentFromProto(ctx, envFromProtoInp, WithEnvGroupFilter(envNameFilter), WithSecrets())
  145. if err != nil {
  146. return revision, telemetry.Error(ctx, span, err, "error getting app environment from revision")
  147. }
  148. if len(envGroups) > 1 {
  149. return revision, telemetry.Error(ctx, span, err, "multiple app envs groups returned for same name")
  150. }
  151. if len(envGroups) == 1 {
  152. revision.Env = envGroups[0]
  153. }
  154. return revision, nil
  155. }