in_test.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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 in OCI format", func() {
  148. var manifest *v1.Manifest
  149. BeforeEach(func() {
  150. req.Source.Repository = "concourse/test-image-static"
  151. req.Params.RawFormat = "oci"
  152. req.Version.Digest, manifest = latestManifest(req.Source.Repository)
  153. })
  154. It("saves the tagged image as image.tar instead of saving the rootfs", func() {
  155. _, err := os.Stat(filepath.Join(destDir, "rootfs"))
  156. Expect(os.IsNotExist(err)).To(BeTrue())
  157. _, err = os.Stat(filepath.Join(destDir, "manifest.json"))
  158. Expect(os.IsNotExist(err)).To(BeTrue())
  159. tag, err := name.NewTag("concourse/test-image-static:latest", name.WeakValidation)
  160. Expect(err).ToNot(HaveOccurred())
  161. img, err := tarball.ImageFromPath(filepath.Join(destDir, "image.tar"), &tag)
  162. Expect(err).ToNot(HaveOccurred())
  163. fetchedManifest, err := img.Manifest()
  164. Expect(err).ToNot(HaveOccurred())
  165. // cannot assert against digest because the saved image's manifest isn't
  166. // JSON-prettified, so it has a different sha256. so just assert against
  167. // digest within manifest, which is what ends up being the 'image id'
  168. // anyway.
  169. Expect(fetchedManifest.Config.Digest).To(Equal(manifest.Config.Digest))
  170. })
  171. })
  172. Describe("saving the digest", func() {
  173. BeforeEach(func() {
  174. req.Source.Repository = "alpine"
  175. req.Version.Digest = latestDigest(req.Source.Repository)
  176. })
  177. It("saves the digest to a file", func() {
  178. digest, err := ioutil.ReadFile(filepath.Join(destDir, "digest"))
  179. Expect(err).ToNot(HaveOccurred())
  180. Expect(string(digest)).To(Equal(req.Version.Digest))
  181. })
  182. })
  183. })