hooks.go 4.1 KB

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