deployment.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. package notifications
  2. import (
  3. "context"
  4. "fmt"
  5. "regexp"
  6. "strings"
  7. "github.com/porter-dev/porter/internal/kubernetes"
  8. "github.com/porter-dev/porter/internal/telemetry"
  9. v1 "k8s.io/api/apps/v1"
  10. )
  11. type Deployment struct {
  12. Status DeploymentStatus `json:"status"`
  13. }
  14. type DeploymentStatus string
  15. const (
  16. DeploymentStatus_Unknown DeploymentStatus = "UNKNOWN"
  17. DeploymentStatus_Pending DeploymentStatus = "PENDING"
  18. DeploymentStatus_Success DeploymentStatus = "SUCCESS"
  19. DeploymentStatus_Failure DeploymentStatus = "FAILURE"
  20. )
  21. type hydrateNotificationInput struct {
  22. Notification
  23. DeploymentTargetId string
  24. Namespace string
  25. K8sAgent *kubernetes.Agent
  26. }
  27. func hydrateNotification(ctx context.Context, inp hydrateNotificationInput) (Notification, error) {
  28. ctx, span := telemetry.NewSpan(ctx, "hydrate-notification")
  29. defer span.End()
  30. hydratedNotification := inp.Notification
  31. if inp.Notification.Deployment.Status != DeploymentStatus_Unknown {
  32. return hydratedNotification, nil
  33. }
  34. if inp.K8sAgent == nil {
  35. err := telemetry.Error(ctx, span, nil, "k8s agent is nil")
  36. return hydratedNotification, err
  37. }
  38. telemetry.WithAttributes(span,
  39. telemetry.AttributeKV{Key: "deployment-target-id", Value: inp.DeploymentTargetId},
  40. telemetry.AttributeKV{Key: "namespace", Value: inp.Namespace},
  41. telemetry.AttributeKV{Key: "app-name", Value: inp.AppName},
  42. telemetry.AttributeKV{Key: "app-revision-id", Value: inp.Notification.AppRevisionID},
  43. telemetry.AttributeKV{Key: "service-name", Value: inp.ServiceName},
  44. )
  45. selectors := []string{
  46. fmt.Sprintf("porter.run/deployment-target-id=%s", inp.DeploymentTargetId),
  47. fmt.Sprintf("porter.run/app-name=%s", inp.AppName),
  48. fmt.Sprintf("porter.run/app-revision-id=%s", inp.Notification.AppRevisionID),
  49. fmt.Sprintf("porter.run/service-name=%s", inp.ServiceName),
  50. }
  51. depls, err := inp.K8sAgent.GetDeploymentsBySelector(inp.Namespace, strings.Join(selectors, ","))
  52. if err != nil {
  53. err := telemetry.Error(ctx, span, err, "failed to get deployments for notification")
  54. return hydratedNotification, err
  55. }
  56. if len(depls.Items) == 0 {
  57. err := telemetry.Error(ctx, span, nil, "no deployments found for notification")
  58. return hydratedNotification, err
  59. }
  60. if len(depls.Items) > 1 {
  61. err := telemetry.Error(ctx, span, nil, "multiple deployments found for notification")
  62. return hydratedNotification, err
  63. }
  64. matchingDeployment := depls.Items[0]
  65. telemetry.WithAttributes(span,
  66. telemetry.AttributeKV{Key: "deployment-name", Value: matchingDeployment.Name},
  67. telemetry.AttributeKV{Key: "deployment-uid", Value: matchingDeployment.ObjectMeta.UID},
  68. telemetry.AttributeKV{Key: "deployment-creation-timestamp", Value: matchingDeployment.ObjectMeta.CreationTimestamp},
  69. )
  70. status := deploymentStatus(matchingDeployment)
  71. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "deployment-status", Value: status})
  72. if status == DeploymentStatus_Unknown {
  73. err := telemetry.Error(ctx, span, nil, "unable to determine status of deployment")
  74. return hydratedNotification, err
  75. }
  76. hydratedNotification.Deployment = Deployment{
  77. Status: status,
  78. }
  79. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "agent-summary", Value: hydratedNotification.AgentSummary})
  80. hydratedNotification.HumanReadableSummary = translateAgentSummary(hydratedNotification, status)
  81. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "human-readable-summary", Value: hydratedNotification.HumanReadableSummary})
  82. return hydratedNotification, nil
  83. }
  84. func deploymentStatus(depl v1.Deployment) DeploymentStatus {
  85. deploymentStatus := DeploymentStatus_Unknown
  86. for _, condition := range depl.Status.Conditions {
  87. if condition.Type == "Progressing" {
  88. if condition.Status == "True" && condition.Reason == "NewReplicaSetAvailable" && depl.Status.ReadyReplicas == depl.Status.Replicas {
  89. deploymentStatus = DeploymentStatus_Success
  90. break
  91. } else if condition.Status == "False" && condition.Reason == "ProgressDeadlineExceeded" {
  92. deploymentStatus = DeploymentStatus_Failure
  93. break
  94. } else {
  95. deploymentStatus = DeploymentStatus_Pending
  96. }
  97. }
  98. }
  99. return deploymentStatus
  100. }
  101. var fatalDeploymentDetailSubstrings = []string{
  102. "stuck in a restart loop",
  103. "restarted with exit code",
  104. }
  105. func detailIndicatesDeploymentFailure(detail string) bool {
  106. // if any of the fatal deployment detail substrings are found in the detail, then the deployment will fail
  107. for _, fatalSubstring := range fatalDeploymentDetailSubstrings {
  108. if strings.Contains(detail, fatalSubstring) {
  109. return true
  110. }
  111. }
  112. return false
  113. }
  114. func translateAgentSummary(notification Notification, status DeploymentStatus) string {
  115. humanReadableSummary := notification.AgentSummary
  116. pattern := `application (\S+) in namespace (\S+)`
  117. regex := regexp.MustCompile(pattern)
  118. if regex.MatchString(humanReadableSummary) {
  119. fmt.Printf("matched regex\n")
  120. humanReadableSummary = regex.ReplaceAllString(humanReadableSummary, fmt.Sprintf("service %s", notification.ServiceName))
  121. }
  122. humanReadableSummary = strings.ReplaceAll(humanReadableSummary, "application", "service")
  123. if status == DeploymentStatus_Pending {
  124. humanReadableSummary = strings.ReplaceAll(humanReadableSummary, "has crashed", "failed to deploy")
  125. humanReadableSummary = strings.ReplaceAll(humanReadableSummary, "crashed", "failed to deploy")
  126. humanReadableSummary = strings.ReplaceAll(humanReadableSummary, "is currently experiencing downtime", "failed to deploy")
  127. }
  128. return humanReadableSummary
  129. }