create.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package stack
  2. import (
  3. "fmt"
  4. "net/http"
  5. "github.com/porter-dev/porter/api/server/handlers"
  6. "github.com/porter-dev/porter/api/server/shared"
  7. "github.com/porter-dev/porter/api/server/shared/apierrors"
  8. "github.com/porter-dev/porter/api/server/shared/config"
  9. "github.com/porter-dev/porter/api/types"
  10. "github.com/porter-dev/porter/internal/encryption"
  11. "github.com/porter-dev/porter/internal/models"
  12. )
  13. type StackCreateHandler struct {
  14. handlers.PorterHandlerReadWriter
  15. }
  16. func NewStackCreateHandler(
  17. config *config.Config,
  18. reader shared.RequestDecoderValidator,
  19. writer shared.ResultWriter,
  20. ) *StackCreateHandler {
  21. return &StackCreateHandler{
  22. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, reader, writer),
  23. }
  24. }
  25. func (p *StackCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  26. proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
  27. cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
  28. namespace, _ := r.Context().Value(types.NamespaceScope).(string)
  29. req := &types.CreateStackRequest{}
  30. if ok := p.DecodeAndValidate(w, r, req); !ok {
  31. return
  32. }
  33. uid, err := encryption.GenerateRandomBytes(16)
  34. if err != nil {
  35. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  36. return
  37. }
  38. sourceConfigs, err := getSourceConfigModels(req.SourceConfigs)
  39. if err != nil {
  40. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  41. return
  42. }
  43. resources, err := getResourceModels(req.AppResources, sourceConfigs)
  44. if err != nil {
  45. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  46. return
  47. }
  48. // write stack to the database with creating status
  49. stack := &models.Stack{
  50. ProjectID: proj.ID,
  51. ClusterID: cluster.ID,
  52. Namespace: namespace,
  53. Name: req.Name,
  54. UID: uid,
  55. Revisions: []models.StackRevision{
  56. {
  57. RevisionNumber: 1,
  58. Status: string(types.StackRevisionStatusDeploying),
  59. SourceConfigs: sourceConfigs,
  60. Resources: resources,
  61. },
  62. },
  63. }
  64. stack, err = p.Repo().Stack().CreateStack(stack)
  65. if err != nil {
  66. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  67. return
  68. }
  69. // update stack revision status
  70. revision := &stack.Revisions[0]
  71. revision.Status = string(types.StackRevisionStatusDeployed)
  72. revision, err = p.Repo().Stack().UpdateStackRevision(revision)
  73. if err != nil {
  74. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  75. return
  76. }
  77. // read the stack again to get the latest revision info
  78. stack, err = p.Repo().Stack().ReadStackByStringID(proj.ID, stack.UID)
  79. if err != nil {
  80. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  81. return
  82. }
  83. w.WriteHeader(http.StatusCreated)
  84. p.WriteResult(w, r, stack.ToStackType())
  85. }
  86. func getSourceConfigModels(sourceConfigs []*types.CreateStackSourceConfigRequest) ([]models.StackSourceConfig, error) {
  87. res := make([]models.StackSourceConfig, 0)
  88. // for now, only write source configs which are deployed as a docker image
  89. // TODO: add parsing/writes for git-based sources
  90. for _, sourceConfig := range sourceConfigs {
  91. if sourceConfig.StackSourceConfigBuild == nil {
  92. uid, err := encryption.GenerateRandomBytes(16)
  93. if err != nil {
  94. return nil, err
  95. }
  96. res = append(res, models.StackSourceConfig{
  97. UID: uid,
  98. Name: sourceConfig.Name,
  99. ImageRepoURI: sourceConfig.ImageRepoURI,
  100. ImageTag: sourceConfig.ImageTag,
  101. })
  102. }
  103. }
  104. return res, nil
  105. }
  106. func getResourceModels(appResources []*types.CreateStackAppResourceRequest, sourceConfigs []models.StackSourceConfig) ([]models.StackResource, error) {
  107. res := make([]models.StackResource, 0)
  108. for _, appResource := range appResources {
  109. uid, err := encryption.GenerateRandomBytes(16)
  110. if err != nil {
  111. return nil, err
  112. }
  113. var linkedSourceConfigUID string
  114. for _, sourceConfig := range sourceConfigs {
  115. if sourceConfig.Name == appResource.SourceConfigName {
  116. linkedSourceConfigUID = sourceConfig.UID
  117. }
  118. }
  119. if linkedSourceConfigUID == "" {
  120. return nil, fmt.Errorf("source config %s does not exist in source config list", appResource.SourceConfigName)
  121. }
  122. res = append(res, models.StackResource{
  123. Name: appResource.Name,
  124. UID: uid,
  125. StackSourceConfigUID: linkedSourceConfigUID,
  126. TemplateRepoURL: appResource.TemplateRepoURL,
  127. TemplateName: appResource.TemplateName,
  128. TemplateVersion: appResource.TemplateVersion,
  129. })
  130. }
  131. return res, nil
  132. }