create.go 8.0 KB

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