build.go 5.7 KB

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