1package resource_test 2 3import ( 4 "errors" 5 "os" 6 "path/filepath" 7 "strings" 8 9 fakesys "github.com/cloudfoundry/bosh-utils/system/fakes" 10 . "github.com/onsi/ginkgo" 11 . "github.com/onsi/gomega" 12 13 fakecrypto "github.com/cloudfoundry/bosh-cli/crypto/fakes" 14 . "github.com/cloudfoundry/bosh-cli/release/resource" 15) 16 17var _ = Describe("FingerprinterImpl", func() { 18 var ( 19 digestCalculator *fakecrypto.FakeDigestCalculator 20 fs *fakesys.FakeFileSystem 21 fingerprinter FingerprinterImpl 22 followSymlinks bool 23 ) 24 25 BeforeEach(func() { 26 digestCalculator = fakecrypto.NewFakeDigestCalculator() 27 fs = fakesys.NewFakeFileSystem() 28 }) 29 30 JustBeforeEach(func() { 31 fingerprinter = NewFingerprinterImpl(digestCalculator, fs, followSymlinks) 32 }) 33 34 Context("successfully creating a fingerprint", func() { 35 var ( 36 chunks []string 37 files []File 38 ) 39 40 BeforeEach(func() { 41 files = []File{ 42 NewFile(filepath.Join("/", "tmp", "file2"), filepath.Join("/", "tmp")), 43 NewFile(filepath.Join("/", "tmp", "file1"), filepath.Join("/", "tmp")), 44 NewFile(filepath.Join("/", "tmp", "file3"), filepath.Join("/", "tmp")), 45 NewFile(filepath.Join("/", "tmp", "rel", "file4"), filepath.Join("/", "tmp")), 46 } 47 48 excludeModeFile := NewFile(filepath.Join("/", "tmp", "file5"), filepath.Join("/", "tmp")) 49 excludeModeFile.ExcludeMode = true 50 files = append(files, excludeModeFile) 51 52 basenameFile := NewFile(filepath.Join("/", "tmp", "rel", "file6"), filepath.Join("/", "tmp")) 53 basenameFile.UseBasename = true 54 files = append(files, basenameFile) 55 56 fs.RegisterOpenFile(filepath.Join("/", "tmp", "file1"), &fakesys.FakeFile{ 57 Stats: &fakesys.FakeFileStats{FileType: fakesys.FakeFileTypeDir}, 58 }) 59 60 fs.RegisterOpenFile(filepath.Join("/", "tmp", "file2"), &fakesys.FakeFile{ 61 Stats: &fakesys.FakeFileStats{FileType: fakesys.FakeFileTypeFile}, 62 }) 63 64 fs.RegisterOpenFile(filepath.Join("/", "tmp", "file3"), &fakesys.FakeFile{ 65 Stats: &fakesys.FakeFileStats{ 66 FileType: fakesys.FakeFileTypeFile, 67 FileMode: os.FileMode(0111), 68 }, 69 }) 70 71 fs.RegisterOpenFile(filepath.Join("/", "tmp", "rel", "file4"), &fakesys.FakeFile{ 72 Stats: &fakesys.FakeFileStats{FileType: fakesys.FakeFileTypeFile}, 73 }) 74 75 fs.RegisterOpenFile(filepath.Join("/", "tmp", "file5"), &fakesys.FakeFile{ 76 Stats: &fakesys.FakeFileStats{FileType: fakesys.FakeFileTypeFile}, 77 }) 78 79 fs.RegisterOpenFile(filepath.Join("/", "tmp", "rel", "file6"), &fakesys.FakeFile{ 80 Stats: &fakesys.FakeFileStats{FileType: fakesys.FakeFileTypeFile}, 81 }) 82 83 digestCalculator.SetCalculateBehavior(map[string]fakecrypto.CalculateInput{ 84 // file1 directory is not sha1-ed 85 filepath.Join("/", "tmp", "file2"): fakecrypto.CalculateInput{DigestStr: "file2-sha1"}, 86 filepath.Join("/", "tmp", "file3"): fakecrypto.CalculateInput{DigestStr: "file3-sha1"}, 87 filepath.Join("/", "tmp", "rel", "file4"): fakecrypto.CalculateInput{DigestStr: "file4-sha1"}, 88 filepath.Join("/", "tmp", "file5"): fakecrypto.CalculateInput{DigestStr: "file5-sha1"}, 89 filepath.Join("/", "tmp", "rel", "file6"): fakecrypto.CalculateInput{DigestStr: "file6-sha1"}, 90 }) 91 92 chunks = []string{ 93 "v2", // version 94 "file1", "40755", // dir perms 95 "file2", "file2-sha1", "100644", // regular file perms 96 "file3", "file3-sha1", "100755", // exec file perms 97 "file5", "file5-sha1", // excludes mode 98 "rel/file4", "file4-sha1", "100644", // relative file 99 "file6", "file6-sha1", "100644", // uses basename 100 "chunk1", ",chunk2", // sorted chunks 101 } 102 }) 103 104 It("fingerprints all files", func() { 105 digestCalculator.CalculateStringInputs = map[string]string{ 106 strings.Join(chunks, ""): "fp", 107 } 108 109 fp, err := fingerprinter.Calculate(files, []string{"chunk2", "chunk1"}) 110 Expect(err).ToNot(HaveOccurred()) 111 Expect(fp).To(Equal("fp")) 112 }) 113 114 It("trims `sha256` algorithm info from resulting fingerprint string", func() { 115 digestCalculator.CalculateStringInputs = map[string]string{ 116 strings.Join(chunks, ""): "sha256:asdfasdfasdfasdf", 117 } 118 119 fp, err := fingerprinter.Calculate(files, []string{"chunk2", "chunk1"}) 120 Expect(err).ToNot(HaveOccurred()) 121 Expect(fp).To(Equal("asdfasdfasdfasdf")) 122 }) 123 }) 124 125 It("returns an error when the resulting checksum contains unexpected content so it does not pass incompatible fingerprints to director", func() { 126 files := []File{NewFile(filepath.Join("/", "tmp", "file"), filepath.Join("/", "tmp"))} 127 fs.WriteFileString(filepath.Join("/", "tmp", "file"), "stuff") 128 129 digestCalculator.CalculateStringInputs = map[string]string{ 130 strings.Join([]string{"v2", "file", "100644"}, ""): "whatTheAlgorithmIsThat!:asdfasdfasdfasdf", 131 } 132 133 _, err := fingerprinter.Calculate(files, []string{}) 134 135 Expect(err).To(HaveOccurred()) 136 Expect(err.Error()).To(ContainSubstring("Generated fingerprint contains unexpected characters 'whatTheAlgorithmIsThat!:asdfasdfasdfasdf'")) 137 }) 138 139 Context("when following symlinks", func() { 140 BeforeEach(func() { 141 followSymlinks = true 142 }) 143 144 It("Includes symlink target in fingerprint calculation", func() { 145 files := []File{ 146 NewFile(filepath.Join("/", "tmp", "regular"), filepath.Join("/", "tmp")), 147 NewFile(filepath.Join("/", "tmp", "symlink"), filepath.Join("/", "tmp")), 148 } 149 150 fs.WriteFileString(filepath.Join("/", "tmp", "regular"), "") 151 fs.Symlink(filepath.Join("/", "tmp", "regular"), filepath.Join("/", "tmp", "symlink")) 152 153 digestCalculator.SetCalculateBehavior(map[string]fakecrypto.CalculateInput{ 154 filepath.Join("/", "tmp", "regular"): fakecrypto.CalculateInput{DigestStr: "regular-sha1"}, 155 }) 156 157 chunks := []string{ 158 "v2", // version 159 "regular", "regular-sha1", "100644", 160 "symlink", "regular-sha1", "100644", 161 "chunk1", ",chunk2", // sorted chunks 162 } 163 164 digestCalculator.CalculateStringInputs = map[string]string{ 165 strings.Join(chunks, ""): "fp", 166 } 167 168 fp, err := fingerprinter.Calculate(files, []string{"chunk2", "chunk1"}) 169 Expect(err).ToNot(HaveOccurred()) 170 Expect(fp).To(Equal("fp")) 171 }) 172 }) 173 174 Context("when not following symlinks", func() { 175 BeforeEach(func() { 176 followSymlinks = false 177 }) 178 179 It("Includes symlink target in fingerprint calculation", func() { 180 files := []File{ 181 NewFile(filepath.Join("/", "tmp", "regular"), filepath.Join("/", "tmp")), 182 NewFile(filepath.Join("/", "tmp", "symlink"), filepath.Join("/", "tmp")), 183 } 184 185 fs.WriteFileString(filepath.Join("/", "tmp", "regular"), "") 186 fs.Symlink("nothing", filepath.Join("/", "tmp", "symlink")) 187 188 digestCalculator.SetCalculateBehavior(map[string]fakecrypto.CalculateInput{ 189 filepath.Join("/", "tmp", "regular"): fakecrypto.CalculateInput{DigestStr: "regular-sha1"}, 190 }) 191 192 chunks := []string{ 193 "v2", // version 194 "regular", "regular-sha1", "100644", 195 "symlink", "symlink-target-sha1", "symlink", 196 "chunk1", ",chunk2", // sorted chunks 197 } 198 199 digestCalculator.CalculateStringInputs = map[string]string{ 200 "nothing": "symlink-target-sha1", 201 strings.Join(chunks, ""): "fp", 202 } 203 204 fp, err := fingerprinter.Calculate(files, []string{"chunk2", "chunk1"}) 205 Expect(err).ToNot(HaveOccurred()) 206 Expect(fp).To(Equal("fp")) 207 }) 208 }) 209 210 It("returns error if stating file fails", func() { 211 fs.RegisterOpenFile(filepath.Join("/", "tmp", "file2"), &fakesys.FakeFile{ 212 StatErr: errors.New("fake-err"), 213 }) 214 215 _, err := fingerprinter.Calculate([]File{NewFile(filepath.Join("/", "tmp", "file2"), filepath.Join("/", "tmp"))}, nil) 216 Expect(err).To(HaveOccurred()) 217 Expect(err.Error()).To(ContainSubstring("fake-err")) 218 }) 219 220 It("returns error if calculating file sha1 fails", func() { 221 fs.RegisterOpenFile(filepath.Join("/", "tmp", "file2"), &fakesys.FakeFile{ 222 Stats: &fakesys.FakeFileStats{FileType: fakesys.FakeFileTypeFile}, 223 }) 224 225 digestCalculator.SetCalculateBehavior(map[string]fakecrypto.CalculateInput{ 226 filepath.Join("/", "tmp", "file2"): fakecrypto.CalculateInput{Err: errors.New("fake-err")}, 227 }) 228 229 _, err := fingerprinter.Calculate([]File{NewFile(filepath.Join("/", "tmp", "file2"), filepath.Join("/", "tmp"))}, nil) 230 Expect(err).To(HaveOccurred()) 231 Expect(err.Error()).To(ContainSubstring("fake-err")) 232 }) 233}) 234