in_test.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. package resource_test
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "io/ioutil"
  6. "os"
  7. "os/exec"
  8. "path/filepath"
  9. "syscall"
  10. "time"
  11. "github.com/google/go-containerregistry/pkg/name"
  12. "github.com/google/go-containerregistry/pkg/v1"
  13. "github.com/google/go-containerregistry/pkg/v1/tarball"
  14. . "github.com/onsi/ginkgo"
  15. . "github.com/onsi/gomega"
  16. "github.com/concourse/registry-image-resource"
  17. )
  18. var _ = Describe("In", func() {
  19. var destDir string
  20. var req struct {
  21. Source resource.Source
  22. Params resource.GetParams
  23. Version resource.Version
  24. }
  25. var res struct {
  26. Version resource.Version
  27. Metadata []resource.MetadataField
  28. }
  29. rootfsPath := func(path ...string) string {
  30. return filepath.Join(append([]string{destDir, "rootfs"}, path...)...)
  31. }
  32. BeforeEach(func() {
  33. var err error
  34. destDir, err = ioutil.TempDir("", "docker-image-in-dir")
  35. Expect(err).ToNot(HaveOccurred())
  36. })
  37. AfterEach(func() {
  38. Expect(os.RemoveAll(destDir)).To(Succeed())
  39. })
  40. JustBeforeEach(func() {
  41. cmd := exec.Command(bins.In, destDir)
  42. payload, err := json.Marshal(req)
  43. Expect(err).ToNot(HaveOccurred())
  44. outBuf := new(bytes.Buffer)
  45. cmd.Stdin = bytes.NewBuffer(payload)
  46. cmd.Stdout = outBuf
  47. cmd.Stderr = GinkgoWriter
  48. err = cmd.Run()
  49. Expect(err).ToNot(HaveOccurred())
  50. err = json.Unmarshal(outBuf.Bytes(), &res)
  51. Expect(err).ToNot(HaveOccurred())
  52. })
  53. Describe("image metadata", func() {
  54. BeforeEach(func() {
  55. req.Source.Repository = "concourse/test-image-metadata"
  56. req.Version.Digest = latestDigest(req.Source.Repository)
  57. })
  58. It("captures the env and user", func() {
  59. var meta struct {
  60. User string `json:"user"`
  61. Env []string `json:"env"`
  62. }
  63. md, err := os.Open(filepath.Join(destDir, "metadata.json"))
  64. Expect(err).ToNot(HaveOccurred())
  65. defer md.Close()
  66. json.NewDecoder(md).Decode(&meta)
  67. Expect(meta.User).To(Equal("someuser"))
  68. Expect(meta.Env).To(Equal([]string{
  69. "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  70. "FOO=1",
  71. }))
  72. })
  73. })
  74. Describe("file attributes", func() {
  75. BeforeEach(func() {
  76. req.Source.Repository = "concourse/test-image-file-perms-mtime"
  77. req.Version.Digest = latestDigest(req.Source.Repository)
  78. })
  79. It("keeps file ownership, permissions, and modified times", func() {
  80. stat, err := os.Stat(rootfsPath("home", "alex", "birthday"))
  81. Expect(err).ToNot(HaveOccurred())
  82. Expect(stat.Mode()).To(Equal(os.FileMode(0603)))
  83. Expect(stat.ModTime()).To(BeTemporally("==", time.Date(1991, 06, 03, 05, 30, 30, 0, time.UTC)))
  84. sys, ok := stat.Sys().(*syscall.Stat_t)
  85. Expect(ok).To(BeTrue())
  86. Expect(sys.Uid).To(Equal(uint32(1000)))
  87. Expect(sys.Gid).To(Equal(uint32(1000)))
  88. })
  89. })
  90. Describe("removed files in layers", func() {
  91. BeforeEach(func() {
  92. req.Source.Repository = "concourse/test-image-whiteout"
  93. req.Version.Digest = latestDigest(req.Source.Repository)
  94. })
  95. It("does not restore files that were removed in later layers", func() {
  96. infos, err := ioutil.ReadDir(rootfsPath("top-dir-1"))
  97. Expect(err).ToNot(HaveOccurred())
  98. Expect(infos).To(HaveLen(2))
  99. stat, err := os.Stat(rootfsPath("top-dir-1", "nested-file"))
  100. Expect(err).ToNot(HaveOccurred())
  101. Expect(stat.IsDir()).To(BeFalse())
  102. stat, err = os.Stat(rootfsPath("top-dir-1", "nested-dir"))
  103. Expect(err).ToNot(HaveOccurred())
  104. Expect(stat.IsDir()).To(BeTrue())
  105. infos, err = ioutil.ReadDir(rootfsPath("top-dir-1", "nested-dir"))
  106. Expect(err).ToNot(HaveOccurred())
  107. Expect(infos).To(HaveLen(3))
  108. stat, err = os.Stat(rootfsPath("top-dir-1", "nested-dir", "file-gone"))
  109. Expect(err).To(HaveOccurred())
  110. stat, err = os.Stat(rootfsPath("top-dir-1", "nested-dir", "file-here"))
  111. Expect(err).ToNot(HaveOccurred())
  112. Expect(stat.IsDir()).To(BeFalse())
  113. stat, err = os.Stat(rootfsPath("top-dir-1", "nested-dir", "file-recreated"))
  114. Expect(err).ToNot(HaveOccurred())
  115. Expect(stat.IsDir()).To(BeFalse())
  116. stat, err = os.Stat(rootfsPath("top-dir-1", "nested-dir", "file-then-dir"))
  117. Expect(err).ToNot(HaveOccurred())
  118. Expect(stat.IsDir()).To(BeTrue())
  119. stat, err = os.Stat(rootfsPath("top-dir-2"))
  120. Expect(err).To(HaveOccurred())
  121. })
  122. })
  123. Describe("a hardlink that is later removed", func() {
  124. BeforeEach(func() {
  125. req.Source.Repository = "concourse/test-image-removed-hardlinks"
  126. req.Version.Digest = latestDigest(req.Source.Repository)
  127. })
  128. It("works", func() {
  129. lstat, err := os.Lstat(rootfsPath("usr", "libexec", "git-core", "git"))
  130. Expect(err).ToNot(HaveOccurred())
  131. Expect(lstat.Mode() & os.ModeSymlink).To(BeZero())
  132. stat, err := os.Stat(rootfsPath("usr", "libexec", "git-core", "git"))
  133. Expect(err).ToNot(HaveOccurred())
  134. Expect(stat.Mode() & os.ModeSymlink).To(BeZero())
  135. })
  136. })
  137. Describe("layers that replace symlinks with regular files", func() {
  138. BeforeEach(func() {
  139. req.Source.Repository = "concourse/test-image-symlinks"
  140. req.Version.Digest = latestDigest(req.Source.Repository)
  141. })
  142. It("removes the symlink and writes to a new file rather than trying to open and write to it (thereby overwriting its target)", func() {
  143. Expect(cat(rootfsPath("a"))).To(Equal("symlinked\n"))
  144. Expect(cat(rootfsPath("b"))).To(Equal("replaced\n"))
  145. })
  146. })
  147. Describe("fetching from a private repository with credentials", func() {
  148. BeforeEach(func() {
  149. req.Source = resource.Source{
  150. Repository: dockerPrivateRepo,
  151. Tag: "latest",
  152. Username: dockerUsername,
  153. Password: dockerPassword,
  154. }
  155. checkDockerUserConfigured()
  156. req.Version = resource.Version{
  157. Digest: PRIVATE_LATEST_STATIC_DIGEST,
  158. }
  159. })
  160. It("works", func() {
  161. Expect(cat(rootfsPath("Dockerfile"))).To(ContainSubstring("hello!"))
  162. })
  163. })
  164. Describe("fetching in OCI format", func() {
  165. var manifest *v1.Manifest
  166. BeforeEach(func() {
  167. req.Source.Repository = "concourse/test-image-static"
  168. req.Params.RawFormat = "oci"
  169. req.Version.Digest, manifest = latestManifest(req.Source.Repository)
  170. })
  171. It("saves the tagged image as image.tar instead of saving the rootfs", func() {
  172. _, err := os.Stat(filepath.Join(destDir, "rootfs"))
  173. Expect(os.IsNotExist(err)).To(BeTrue())
  174. _, err = os.Stat(filepath.Join(destDir, "manifest.json"))
  175. Expect(os.IsNotExist(err)).To(BeTrue())
  176. tag, err := name.NewTag("concourse/test-image-static:latest", name.WeakValidation)
  177. Expect(err).ToNot(HaveOccurred())
  178. img, err := tarball.ImageFromPath(filepath.Join(destDir, "image.tar"), &tag)
  179. Expect(err).ToNot(HaveOccurred())
  180. fetchedManifest, err := img.Manifest()
  181. Expect(err).ToNot(HaveOccurred())
  182. // cannot assert against digest because the saved image's manifest isn't
  183. // JSON-prettified, so it has a different sha256. so just assert against
  184. // digest within manifest, which is what ends up being the 'image id'
  185. // anyway.
  186. Expect(fetchedManifest.Config.Digest).To(Equal(manifest.Config.Digest))
  187. })
  188. })
  189. Describe("saving the digest", func() {
  190. BeforeEach(func() {
  191. req.Source.Repository = "alpine"
  192. req.Version.Digest = latestDigest(req.Source.Repository)
  193. })
  194. It("saves the digest to a file", func() {
  195. digest, err := ioutil.ReadFile(filepath.Join(destDir, "digest"))
  196. Expect(err).ToNot(HaveOccurred())
  197. Expect(string(digest)).To(Equal(req.Version.Digest))
  198. })
  199. })
  200. })