create.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. package porter_app
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "github.com/porter-dev/porter/api/types"
  7. "github.com/porter-dev/porter/internal/models"
  8. "github.com/porter-dev/porter/internal/repository"
  9. "github.com/porter-dev/porter/internal/telemetry"
  10. )
  11. // ErrMissingSourceType is returned when the source type is not specified
  12. var ErrMissingSourceType = errors.New("missing source type")
  13. // SourceType is a string type specifying the source type of an app. This is specified in the incoming request
  14. type SourceType string
  15. const (
  16. // SourceType_Github is the source kind for a github repo
  17. SourceType_Github SourceType = "github"
  18. // SourceType_DockerRegistry is the source kind for an app using an image from a docker registry
  19. SourceType_DockerRegistry SourceType = "docker-registry"
  20. // SourceType_Local is the source kind for an app being built locally
  21. SourceType_Local SourceType = "other"
  22. )
  23. // Image is the image used by an app with a docker registry source
  24. type Image struct {
  25. Repository string `json:"repository"`
  26. Tag string `json:"tag"`
  27. }
  28. // CreateGithubAppInput is the input for creating an app with a github source
  29. type CreateGithubAppInput struct {
  30. ProjectID uint
  31. ClusterID uint
  32. Name string
  33. GitBranch string
  34. GitRepoName string
  35. PorterYamlPath string
  36. GitRepoID uint
  37. PorterAppRepository repository.PorterAppRepository
  38. }
  39. // CreateDockerRegistryAppInput is the input for creating an app with a docker registry source
  40. type CreateDockerRegistryAppInput struct {
  41. ProjectID uint
  42. ClusterID uint
  43. Name string
  44. Repository string
  45. Tag string
  46. PorterAppRepository repository.PorterAppRepository
  47. }
  48. // CreateLocalAppInput is the input for creating an app that is built locally via the cli
  49. type CreateLocalAppInput struct {
  50. ProjectID uint
  51. ClusterID uint
  52. Name string
  53. PorterAppRepository repository.PorterAppRepository
  54. }
  55. // CreateOrGetAppRecordInput is the input to the CreateOrGetAppRecord function
  56. type CreateOrGetAppRecordInput struct {
  57. ClusterID uint
  58. ProjectID uint
  59. Name string
  60. SourceType SourceType
  61. GitBranch string
  62. GitRepoName string
  63. GitRepoID uint
  64. PorterYamlPath string
  65. Image *Image
  66. PorterAppRepository repository.PorterAppRepository
  67. }
  68. // CreateOrGetAppRecord creates an app based on the input or gets an existing app if one is found with the provided name
  69. func CreateOrGetAppRecord(ctx context.Context, input CreateOrGetAppRecordInput) (*types.PorterApp, error) {
  70. ctx, span := telemetry.NewSpan(ctx, "porter-app-create")
  71. defer span.End()
  72. var app *types.PorterApp
  73. if input.ClusterID == 0 {
  74. return app, telemetry.Error(ctx, span, nil, "cluster id is 0")
  75. }
  76. if input.ProjectID == 0 {
  77. return app, telemetry.Error(ctx, span, nil, "project id is 0")
  78. }
  79. if input.Name == "" {
  80. return app, telemetry.Error(ctx, span, nil, "name is empty")
  81. }
  82. telemetry.WithAttributes(span,
  83. telemetry.AttributeKV{Key: "project-id", Value: input.ProjectID},
  84. telemetry.AttributeKV{Key: "name", Value: input.Name},
  85. )
  86. porterAppDBEntries, err := input.PorterAppRepository.ReadPorterAppsByProjectIDAndName(input.ProjectID, input.Name)
  87. if err != nil {
  88. return app, telemetry.Error(ctx, span, err, "error reading porter apps by project id and name")
  89. }
  90. if len(porterAppDBEntries) > 1 {
  91. return app, telemetry.Error(ctx, span, nil, "multiple apps with same name")
  92. }
  93. // return existing app if one found with same name
  94. if len(porterAppDBEntries) == 1 {
  95. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "existing-app-id", Value: porterAppDBEntries[0].ID})
  96. app = porterAppDBEntries[0].ToPorterAppType()
  97. return app, nil
  98. }
  99. switch input.SourceType {
  100. case SourceType_Github:
  101. if input.GitRepoID == 0 {
  102. return app, telemetry.Error(ctx, span, nil, "git repo id is 0")
  103. }
  104. if input.GitBranch == "" {
  105. return app, telemetry.Error(ctx, span, nil, "git branch is empty")
  106. }
  107. if input.GitRepoName == "" {
  108. return app, telemetry.Error(ctx, span, nil, "git repo name is empty")
  109. }
  110. telemetry.WithAttributes(span,
  111. telemetry.AttributeKV{Key: "git-repo-id", Value: input.GitRepoID},
  112. telemetry.AttributeKV{Key: "git-branch", Value: input.GitBranch},
  113. telemetry.AttributeKV{Key: "git-repo-name", Value: input.GitRepoName},
  114. )
  115. input := CreateGithubAppInput{
  116. ProjectID: input.ProjectID,
  117. ClusterID: input.ClusterID,
  118. Name: input.Name,
  119. GitRepoID: input.GitRepoID,
  120. GitBranch: input.GitBranch,
  121. GitRepoName: input.GitRepoName,
  122. PorterYamlPath: input.PorterYamlPath,
  123. PorterAppRepository: input.PorterAppRepository,
  124. }
  125. githubApp, err := createGithubApp(ctx, input)
  126. if err != nil {
  127. return app, telemetry.Error(ctx, span, err, "error creating github app")
  128. }
  129. app = githubApp.ToPorterAppType()
  130. case SourceType_DockerRegistry:
  131. if input.Image == nil {
  132. return app, telemetry.Error(ctx, span, nil, "image is required")
  133. }
  134. telemetry.WithAttributes(span,
  135. telemetry.AttributeKV{Key: "image-repo-uri", Value: fmt.Sprintf("%s:%s", input.Image.Repository, input.Image.Tag)},
  136. )
  137. input := CreateDockerRegistryAppInput{
  138. ProjectID: input.ProjectID,
  139. ClusterID: input.ClusterID,
  140. Name: input.Name,
  141. Repository: input.Image.Repository,
  142. Tag: input.Image.Tag,
  143. PorterAppRepository: input.PorterAppRepository,
  144. }
  145. dockerApp, err := createDockerRegistryApp(ctx, input)
  146. if err != nil {
  147. return app, telemetry.Error(ctx, span, err, "error creating docker registry app")
  148. }
  149. app = dockerApp.ToPorterAppType()
  150. case SourceType_Local:
  151. input := CreateLocalAppInput{
  152. ProjectID: input.ProjectID,
  153. ClusterID: input.ClusterID,
  154. Name: input.Name,
  155. PorterAppRepository: input.PorterAppRepository,
  156. }
  157. localApp, err := createLocalApp(ctx, input)
  158. if err != nil {
  159. return app, telemetry.Error(ctx, span, err, "error creating local app")
  160. }
  161. app = localApp.ToPorterAppType()
  162. default:
  163. return app, telemetry.Error(ctx, span, ErrMissingSourceType, "missing source type")
  164. }
  165. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "app-id", Value: app.ID})
  166. return app, nil
  167. }
  168. func createGithubApp(ctx context.Context, input CreateGithubAppInput) (*models.PorterApp, error) {
  169. ctx, span := telemetry.NewSpan(ctx, "create-github-app")
  170. defer span.End()
  171. porterApp := &models.PorterApp{
  172. Name: input.Name,
  173. ProjectID: input.ProjectID,
  174. ClusterID: input.ClusterID,
  175. GitRepoID: input.GitRepoID,
  176. GitBranch: input.GitBranch,
  177. RepoName: input.GitRepoName,
  178. PorterYamlPath: input.PorterYamlPath,
  179. }
  180. porterApp, err := input.PorterAppRepository.CreatePorterApp(porterApp)
  181. if err != nil {
  182. return porterApp, telemetry.Error(ctx, span, err, "error creating porter app")
  183. }
  184. return porterApp, nil
  185. }
  186. func createDockerRegistryApp(ctx context.Context, input CreateDockerRegistryAppInput) (*models.PorterApp, error) {
  187. ctx, span := telemetry.NewSpan(ctx, "create-docker-registry-app")
  188. defer span.End()
  189. porterApp := &models.PorterApp{
  190. Name: input.Name,
  191. ProjectID: input.ProjectID,
  192. ClusterID: input.ClusterID,
  193. ImageRepoURI: fmt.Sprintf("%s:%s", input.Repository, input.Tag),
  194. }
  195. porterApp, err := input.PorterAppRepository.CreatePorterApp(porterApp)
  196. if err != nil {
  197. return porterApp, telemetry.Error(ctx, span, err, "error creating porter app")
  198. }
  199. return porterApp, nil
  200. }
  201. // createLocalApp creates an app that is built locally via the cli, usually frmo a git repo
  202. // a local app will not have the same repo metadata as a github app
  203. func createLocalApp(ctx context.Context, input CreateLocalAppInput) (*models.PorterApp, error) {
  204. ctx, span := telemetry.NewSpan(ctx, "create-local-app")
  205. defer span.End()
  206. porterApp := &models.PorterApp{
  207. Name: input.Name,
  208. ProjectID: input.ProjectID,
  209. ClusterID: input.ClusterID,
  210. }
  211. porterApp, err := input.PorterAppRepository.CreatePorterApp(porterApp)
  212. if err != nil {
  213. return porterApp, telemetry.Error(ctx, span, err, "error creating porter app")
  214. }
  215. return porterApp, nil
  216. }