2
0

hooks.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package stack
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "fmt"
  6. "os"
  7. "strings"
  8. "github.com/fatih/color"
  9. api "github.com/porter-dev/porter/api/client"
  10. "github.com/porter-dev/porter/api/types"
  11. "github.com/porter-dev/porter/cli/cmd/config"
  12. )
  13. type DeployAppHook struct {
  14. Client *api.Client
  15. ApplicationName string
  16. ProjectID, ClusterID uint
  17. BuildImageDriverName string
  18. PorterYAML []byte
  19. Builder string
  20. BuildEventID string
  21. }
  22. func (t *DeployAppHook) PreApply() error {
  23. err := config.ValidateCLIEnvironment()
  24. if err != nil {
  25. errMsg := composePreviewMessage("porter CLI is not configured correctly", Error)
  26. return fmt.Errorf("%s: %w", errMsg, err)
  27. }
  28. if os.Getenv("GITHUB_RUN_ID") != "" {
  29. buildEventId, err := createAppEvent(t.Client, t.ApplicationName, t.ProjectID, t.ClusterID)
  30. if err != nil {
  31. return err
  32. }
  33. t.BuildEventID = buildEventId
  34. }
  35. return nil
  36. }
  37. func (t *DeployAppHook) DataQueries() map[string]interface{} {
  38. res := map[string]interface{}{
  39. "image": fmt.Sprintf("{$.%s.image}", t.BuildImageDriverName),
  40. }
  41. return res
  42. }
  43. // deploy the app
  44. func (t *DeployAppHook) PostApply(driverOutput map[string]interface{}) error {
  45. eventRequest := types.CreateOrUpdatePorterAppEventRequest{
  46. Status: "SUCCESS",
  47. Type: types.PorterAppEventType_Build,
  48. Metadata: map[string]any{},
  49. ID: t.BuildEventID,
  50. }
  51. _, _ = t.Client.CreateOrUpdatePorterAppEvent(context.Background(), t.ProjectID, t.ClusterID, t.ApplicationName, &eventRequest)
  52. namespace := fmt.Sprintf("porter-stack-%s", t.ApplicationName)
  53. _, err := t.Client.GetRelease(
  54. context.Background(),
  55. t.ProjectID,
  56. t.ClusterID,
  57. namespace,
  58. t.ApplicationName,
  59. )
  60. shouldCreate := err != nil
  61. if err != nil {
  62. color.New(color.FgYellow).Printf("Could not read release for app %s (%s): attempting creation\n", t.ApplicationName, err.Error())
  63. } else {
  64. color.New(color.FgGreen).Printf("Found release for app %s: attempting update\n", t.ApplicationName)
  65. }
  66. return t.applyApp(shouldCreate, driverOutput)
  67. }
  68. func (t *DeployAppHook) applyApp(shouldCreate bool, driverOutput map[string]interface{}) error {
  69. var imageInfo types.ImageInfo
  70. image, ok := driverOutput["image"].(string)
  71. // if it contains a $, then it means the query didn't resolve to anything
  72. if ok && !strings.Contains(image, "$") {
  73. imageSpl := strings.Split(image, ":")
  74. if len(imageSpl) == 2 {
  75. imageInfo = types.ImageInfo{
  76. Repository: imageSpl[0],
  77. Tag: imageSpl[1],
  78. }
  79. } else {
  80. return fmt.Errorf("could not parse image info %s", image)
  81. }
  82. }
  83. _, err := t.Client.CreatePorterApp(
  84. context.Background(),
  85. t.ProjectID,
  86. t.ClusterID,
  87. t.ApplicationName,
  88. &types.CreatePorterAppRequest{
  89. ClusterID: t.ClusterID,
  90. ProjectID: t.ProjectID,
  91. PorterYAMLBase64: base64.StdEncoding.EncodeToString(t.PorterYAML),
  92. ImageInfo: imageInfo,
  93. OverrideRelease: false, // deploying from the cli will never delete release resources, only append or override
  94. Builder: t.Builder,
  95. },
  96. )
  97. if err != nil {
  98. if shouldCreate {
  99. return fmt.Errorf("error creating app %s: %w", t.ApplicationName, err)
  100. }
  101. return fmt.Errorf("error updating app %s: %w", t.ApplicationName, err)
  102. }
  103. return nil
  104. }
  105. func (t *DeployAppHook) OnConsolidatedErrors(errors map[string]error) {
  106. errorStringMap := make(map[string]string)
  107. for k, v := range errors {
  108. errorStringMap[k] = v.Error()
  109. }
  110. eventRequest := types.CreateOrUpdatePorterAppEventRequest{
  111. Status: "FAILED",
  112. Type: types.PorterAppEventType_Build,
  113. Metadata: map[string]any{
  114. "errors": errorStringMap,
  115. },
  116. ID: t.BuildEventID,
  117. }
  118. _, _ = t.Client.CreateOrUpdatePorterAppEvent(context.Background(), t.ProjectID, t.ClusterID, t.ApplicationName, &eventRequest)
  119. }
  120. func (t *DeployAppHook) OnError(err error) {
  121. t.OnConsolidatedErrors(map[string]error{
  122. "pre-apply": err,
  123. })
  124. }