build.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. package v2
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "github.com/porter-dev/porter/api/types"
  10. "github.com/porter-dev/porter/cli/cmd/pack"
  11. "github.com/porter-dev/porter/cli/cmd/docker"
  12. api "github.com/porter-dev/porter/api/client"
  13. )
  14. const (
  15. buildMethodPack = "pack"
  16. buildMethodDocker = "docker"
  17. )
  18. // buildInput is the input struct for the build method
  19. type buildInput struct {
  20. ProjectID uint
  21. // AppName is the name of the application being built and is used to name the repository
  22. AppName string
  23. BuildContext string
  24. Dockerfile string
  25. BuildMethod string
  26. // Builder is the image containing the components necessary to build the application in a pack build
  27. Builder string
  28. BuildPacks []string
  29. // ImageTag is the tag to apply to the new image
  30. ImageTag string
  31. // CurrentImageTag is used in docker build to cache from
  32. CurrentImageTag string
  33. RepositoryURL string
  34. }
  35. // build will create an image repository if it does not exist, and then build and push the image
  36. func build(ctx context.Context, client api.Client, inp buildInput) error {
  37. if inp.ProjectID == 0 {
  38. return errors.New("must specify a project id")
  39. }
  40. projectID := inp.ProjectID
  41. if inp.ImageTag == "" {
  42. return errors.New("must specify an image tag")
  43. }
  44. tag := inp.ImageTag
  45. if inp.RepositoryURL == "" {
  46. return errors.New("must specify a registry url")
  47. }
  48. imageURL := strings.TrimPrefix(inp.RepositoryURL, "https://")
  49. err := createImageRepositoryIfNotExists(ctx, client, projectID, imageURL)
  50. if err != nil {
  51. return fmt.Errorf("error creating image repository: %w", err)
  52. }
  53. dockerAgent, err := docker.NewAgentWithAuthGetter(ctx, client, projectID)
  54. if err != nil {
  55. return fmt.Errorf("error getting docker agent: %w", err)
  56. }
  57. switch inp.BuildMethod {
  58. case buildMethodDocker:
  59. basePath, err := filepath.Abs(".")
  60. if err != nil {
  61. return fmt.Errorf("error getting absolute path: %w", err)
  62. }
  63. buildCtx, dockerfilePath, isDockerfileInCtx, err := resolveDockerPaths(
  64. basePath,
  65. inp.BuildContext,
  66. inp.Dockerfile,
  67. )
  68. if err != nil {
  69. return fmt.Errorf("error resolving docker paths: %w", err)
  70. }
  71. opts := &docker.BuildOpts{
  72. ImageRepo: inp.RepositoryURL,
  73. Tag: tag,
  74. CurrentTag: inp.CurrentImageTag,
  75. BuildContext: buildCtx,
  76. DockerfilePath: dockerfilePath,
  77. IsDockerfileInCtx: isDockerfileInCtx,
  78. }
  79. err = dockerAgent.BuildLocal(
  80. ctx,
  81. opts,
  82. )
  83. if err != nil {
  84. return fmt.Errorf("error building image with docker: %w", err)
  85. }
  86. case buildMethodPack:
  87. packAgent := &pack.Agent{}
  88. opts := &docker.BuildOpts{
  89. ImageRepo: imageURL,
  90. Tag: tag,
  91. BuildContext: inp.BuildContext,
  92. }
  93. buildConfig := &types.BuildConfig{
  94. Builder: inp.Builder,
  95. Buildpacks: inp.BuildPacks,
  96. }
  97. err := packAgent.Build(ctx, opts, buildConfig, "")
  98. if err != nil {
  99. return fmt.Errorf("error building image with pack: %w", err)
  100. }
  101. default:
  102. return fmt.Errorf("invalid build method: %s", inp.BuildMethod)
  103. }
  104. err = dockerAgent.PushImage(ctx, fmt.Sprintf("%s:%s", imageURL, tag))
  105. if err != nil {
  106. return fmt.Errorf("error pushing image url: %w\n", err)
  107. }
  108. return nil
  109. }
  110. func createImageRepositoryIfNotExists(ctx context.Context, client api.Client, projectID uint, imageURL string) error {
  111. if projectID == 0 {
  112. return errors.New("must specify a project id")
  113. }
  114. if imageURL == "" {
  115. return errors.New("must specify an image url")
  116. }
  117. regList, err := client.ListRegistries(ctx, projectID)
  118. if err != nil {
  119. return fmt.Errorf("error calling list registries: %w", err)
  120. }
  121. if regList == nil {
  122. return errors.New("registry list is nil")
  123. }
  124. if len(*regList) == 0 {
  125. return errors.New("no registries found for project")
  126. }
  127. var registryID uint
  128. for _, registry := range *regList {
  129. if strings.Contains(strings.TrimPrefix(imageURL, "https://"), strings.TrimPrefix(registry.URL, "https://")) {
  130. registryID = registry.ID
  131. break
  132. }
  133. }
  134. if registryID == 0 {
  135. return errors.New("no registries match url")
  136. }
  137. err = client.CreateRepository(
  138. ctx,
  139. projectID,
  140. registryID,
  141. &types.CreateRegistryRepositoryRequest{
  142. ImageRepoURI: imageURL,
  143. },
  144. )
  145. if err != nil {
  146. return fmt.Errorf("error creating repository: %w", err)
  147. }
  148. return nil
  149. }
  150. // resolveDockerPaths returns a path to the dockerfile that is either relative or absolute, and a path
  151. // to the build context that is absolute.
  152. //
  153. // The return value will be relative if the dockerfile exists within the build context, absolute
  154. // otherwise. The second return value is true if the dockerfile exists within the build context,
  155. // false otherwise.
  156. func resolveDockerPaths(basePath string, buildContextPath string, dockerfilePath string) (
  157. absoluteBuildContextPath string,
  158. outputDockerfilePath string,
  159. isDockerfileRelative bool,
  160. err error,
  161. ) {
  162. absoluteBuildContextPath, err = filepath.Abs(buildContextPath)
  163. if err != nil {
  164. return "", "", false, fmt.Errorf("error getting absolute path: %w", err)
  165. }
  166. outputDockerfilePath = dockerfilePath
  167. if !filepath.IsAbs(dockerfilePath) {
  168. outputDockerfilePath = filepath.Join(basePath, dockerfilePath)
  169. }
  170. pathComp, err := filepath.Rel(absoluteBuildContextPath, outputDockerfilePath)
  171. if err != nil {
  172. return "", "", false, fmt.Errorf("error getting relative path: %w", err)
  173. }
  174. if !strings.HasPrefix(pathComp, ".."+string(os.PathSeparator)) {
  175. isDockerfileRelative = true
  176. return absoluteBuildContextPath, pathComp, isDockerfileRelative, nil
  177. }
  178. isDockerfileRelative = false
  179. outputDockerfilePath, err = filepath.Abs(outputDockerfilePath)
  180. if err != nil {
  181. return "", "", false, err
  182. }
  183. return absoluteBuildContextPath, outputDockerfilePath, isDockerfileRelative, nil
  184. }