github.com/cloudfoundry-attic/ltc@v0.0.0-20151123212628-098adc7919fc/docker_runner/docker_metadata_fetcher/docker_metadata_fetcher_test.go (about) 1 package docker_metadata_fetcher_test 2 3 import ( 4 "errors" 5 6 . "github.com/onsi/ginkgo" 7 . "github.com/onsi/gomega" 8 9 "github.com/cloudfoundry-incubator/ltc/docker_runner/docker_metadata_fetcher" 10 "github.com/cloudfoundry-incubator/ltc/docker_runner/docker_metadata_fetcher/fake_docker_session" 11 "github.com/docker/docker/registry" 12 ) 13 14 var _ = Describe("DockerMetaDataFetcher", func() { 15 var ( 16 fakeDockerSessionFactory *fake_docker_session.FakeDockerSessionFactory 17 fakeDockerSession *fake_docker_session.FakeDockerSession 18 dockerMetadataFetcher docker_metadata_fetcher.DockerMetadataFetcher 19 ) 20 21 BeforeEach(func() { 22 fakeDockerSession = &fake_docker_session.FakeDockerSession{} 23 fakeDockerSessionFactory = &fake_docker_session.FakeDockerSessionFactory{} 24 dockerMetadataFetcher = docker_metadata_fetcher.New(fakeDockerSessionFactory) 25 }) 26 27 Describe("FetchMetadata", func() { 28 29 Context("when fetching metadata from the docker hub registry", func() { 30 It("returns the ImageMetadata with the WorkingDir, StartCommand, and PortConfig, and sets the monitored port to the lowest exposed tcp port", func() { 31 fakeDockerSessionFactory.MakeSessionReturns(fakeDockerSession, nil) 32 imageList := map[string]*registry.ImgData{ 33 "29d531509fb": ®istry.ImgData{ 34 ID: "29d531509fb", 35 Checksum: "dsflksdfjlkj", 36 ChecksumPayload: "sdflksdjfkl", 37 Tag: "latest", 38 }, 39 } 40 fakeDockerSession.GetRepositoryDataReturns( 41 ®istry.RepositoryData{ 42 ImgList: imageList, 43 Endpoints: []string{"https://registry-1.docker.io/v1/"}, 44 }, nil) 45 fakeDockerSession.GetRemoteTagsReturns(map[string]string{"latest": "29d531509fb"}, nil) 46 fakeDockerSession.GetRemoteImageJSONReturns( 47 []byte(`{ 48 "container_config":{ "ExposedPorts":{"28321/tcp":{}, "6923/udp":{}, "27017/tcp":{}} }, 49 "config":{ 50 "WorkingDir":"/home/app", 51 "User":"the-meta-user", 52 "Entrypoint":["/lattice-app"], 53 "Cmd":["--enableAwesomeMode=true","iloveargs"], 54 "Env":["A=1","B=2"] 55 } 56 }`), 57 0, 58 nil) 59 dockerPath := "cool_user123/sweetapp:latest" 60 dockerImageNoTag := "cool_user123/sweetapp" 61 62 imageMetadata, err := dockerMetadataFetcher.FetchMetadata(dockerPath) 63 Expect(err).NotTo(HaveOccurred()) 64 Expect(imageMetadata).NotTo(BeNil()) 65 Expect(imageMetadata.User).To(Equal("the-meta-user")) 66 Expect(imageMetadata.WorkingDir).To(Equal("/home/app")) 67 Expect(imageMetadata.StartCommand).To(ConsistOf("/lattice-app", "--enableAwesomeMode=true", "iloveargs")) 68 Expect(imageMetadata.ExposedPorts).To(Equal([]uint16{uint16(27017), uint16(28321)})) 69 Expect(imageMetadata.Env).To(ConsistOf([]string{"A=1", "B=2"})) 70 71 Expect(fakeDockerSessionFactory.MakeSessionCallCount()).To(Equal(1)) 72 Expect(fakeDockerSessionFactory.MakeSessionArgsForCall(0)).To(Equal(dockerImageNoTag)) 73 74 Expect(fakeDockerSession.GetRepositoryDataCallCount()).To(Equal(1)) 75 Expect(fakeDockerSession.GetRepositoryDataArgsForCall(0)).To(Equal(dockerImageNoTag)) 76 77 Expect(fakeDockerSession.GetRemoteTagsCallCount()).To(Equal(1)) 78 registries, repo := fakeDockerSession.GetRemoteTagsArgsForCall(0) 79 Expect(registries).To(ConsistOf("https://registry-1.docker.io/v1/")) 80 Expect(repo).To(Equal("cool_user123/sweetapp")) 81 82 Expect(fakeDockerSession.GetRemoteImageJSONCallCount()).To(Equal(1)) 83 imgIDParam, remoteImageEndpointParam := fakeDockerSession.GetRemoteImageJSONArgsForCall(0) 84 Expect(imgIDParam).To(Equal("29d531509fb")) 85 Expect(remoteImageEndpointParam).To(Equal("https://registry-1.docker.io/v1/")) 86 }) 87 }) 88 89 Context("when fetching metadata from a signed custom registry", func() { 90 It("returns the image metadata", func() { 91 fakeDockerSessionFactory.MakeSessionReturns(fakeDockerSession, nil) 92 imageList := map[string]*registry.ImgData{ 93 "29d531509fb": ®istry.ImgData{ 94 ID: "29d531509fb", 95 Checksum: "dsflksdfjlkj", 96 ChecksumPayload: "sdflksdjfkl", 97 Tag: "latest", 98 }, 99 } 100 fakeDockerSession.GetRepositoryDataReturns( 101 ®istry.RepositoryData{ 102 ImgList: imageList, 103 Endpoints: []string{"http://my.custom.registry:5000/v1/"}, 104 }, nil) 105 fakeDockerSession.GetRemoteTagsReturns(map[string]string{"latest": "29d531509fb"}, nil) 106 fakeDockerSession.GetRemoteImageJSONReturns( 107 []byte(`{ 108 "container_config":{ "ExposedPorts":{"4444/tcp":{}, "5555/udp":{}, "3333/tcp":{}} }, 109 "config":{ 110 "WorkingDir":"/home/app", 111 "User":"the-meta-user", 112 "Entrypoint":["/savory-app"], 113 "Cmd":["--pretzels=salty","cheesy"], 114 "Env":["A=1","B=2"] 115 } 116 }`), 117 0, 118 nil) 119 120 dockerPath := "my.custom.registry:5000/savory-app" 121 imageMetadata, err := dockerMetadataFetcher.FetchMetadata(dockerPath) 122 Expect(err).NotTo(HaveOccurred()) 123 Expect(imageMetadata).NotTo(BeNil()) 124 Expect(imageMetadata.User).To(Equal("the-meta-user")) 125 Expect(imageMetadata.WorkingDir).To(Equal("/home/app")) 126 Expect(imageMetadata.StartCommand).To(ConsistOf("/savory-app", "--pretzels=salty", "cheesy")) 127 Expect(imageMetadata.ExposedPorts).To(ConsistOf(uint16(3333), uint16(4444))) 128 Expect(imageMetadata.Env).To(ConsistOf([]string{"A=1", "B=2"})) 129 130 Expect(fakeDockerSessionFactory.MakeSessionCallCount()).To(Equal(1)) 131 Expect(fakeDockerSessionFactory.MakeSessionArgsForCall(0)).To(Equal(dockerPath)) 132 133 Expect(fakeDockerSession.GetRepositoryDataCallCount()).To(Equal(1)) 134 Expect(fakeDockerSession.GetRepositoryDataArgsForCall(0)).To(Equal("savory-app")) 135 136 Expect(fakeDockerSession.GetRemoteTagsCallCount()).To(Equal(1)) 137 registries, repo := fakeDockerSession.GetRemoteTagsArgsForCall(0) 138 Expect(registries).To(ConsistOf("http://my.custom.registry:5000/v1/")) 139 Expect(repo).To(Equal("savory-app")) 140 141 Expect(fakeDockerSession.GetRemoteImageJSONCallCount()).To(Equal(1)) 142 imgIDParam, remoteImageEndpointParam := fakeDockerSession.GetRemoteImageJSONArgsForCall(0) 143 Expect(imgIDParam).To(Equal("29d531509fb")) 144 Expect(remoteImageEndpointParam).To(Equal("http://my.custom.registry:5000/v1/")) 145 }) 146 }) 147 148 Context("when fetching metadata from a insecure custom registry", func() { 149 It("retries after getting unknown CA error and returns the image metadata", func() { 150 insecureRegistryErrorMessage := "If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry 192.168.11.1:5000` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/192.168.11.1:5000/ca.crt" 151 fakeDockerSessionFactory.MakeSessionStub = func(reposName string, allowInsecure bool) (docker_metadata_fetcher.DockerSession, error) { 152 if !allowInsecure { 153 return fakeDockerSession, errors.New(insecureRegistryErrorMessage) 154 } 155 156 return fakeDockerSession, nil 157 } 158 159 imageList := map[string]*registry.ImgData{ 160 "29d531509fb": ®istry.ImgData{ 161 ID: "29d531509fb", 162 Checksum: "dsflksdfjlkj", 163 ChecksumPayload: "sdflksdjfkl", 164 Tag: "latest", 165 }, 166 } 167 fakeDockerSession.GetRepositoryDataReturns( 168 ®istry.RepositoryData{ 169 ImgList: imageList, 170 Endpoints: []string{"http://my.custom.registry:5000/v1/"}, 171 }, nil) 172 fakeDockerSession.GetRemoteTagsReturns(map[string]string{"latest": "29d531509fb"}, nil) 173 fakeDockerSession.GetRemoteImageJSONReturns( 174 []byte(`{ 175 "container_config":{ "ExposedPorts":{"4444/tcp":{}, "5555/udp":{}, "3333/tcp":{}} }, 176 "config":{ 177 "WorkingDir":"/home/app", 178 "User":"the-meta-user", 179 "Entrypoint":["/savory-app"], 180 "Cmd":["--pretzels=salty","cheesy"], 181 "Env":["A=1","B=2"] 182 } 183 }`), 184 0, 185 nil) 186 187 dockerPath := "my.custom.registry:5000/savory-app" 188 imageMetadata, err := dockerMetadataFetcher.FetchMetadata(dockerPath) 189 Expect(err).NotTo(HaveOccurred()) 190 Expect(imageMetadata).NotTo(BeNil()) 191 Expect(imageMetadata.User).To(Equal("the-meta-user")) 192 Expect(imageMetadata.WorkingDir).To(Equal("/home/app")) 193 Expect(imageMetadata.StartCommand).To(ConsistOf("/savory-app", "--pretzels=salty", "cheesy")) 194 Expect(imageMetadata.ExposedPorts).To(ConsistOf(uint16(3333), uint16(4444))) 195 Expect(imageMetadata.Env).To(ConsistOf([]string{"A=1", "B=2"})) 196 197 Expect(fakeDockerSessionFactory.MakeSessionCallCount()).To(Equal(2)) 198 199 reposName, allowInsecure := fakeDockerSessionFactory.MakeSessionArgsForCall(0) 200 Expect(reposName).To(Equal(dockerPath)) 201 Expect(allowInsecure).To(BeFalse()) 202 203 reposName, allowInsecure = fakeDockerSessionFactory.MakeSessionArgsForCall(1) 204 Expect(reposName).To(Equal(dockerPath)) 205 Expect(allowInsecure).To(BeTrue()) 206 207 Expect(fakeDockerSession.GetRepositoryDataCallCount()).To(Equal(1)) 208 Expect(fakeDockerSession.GetRepositoryDataArgsForCall(0)).To(Equal("savory-app")) 209 210 Expect(fakeDockerSession.GetRemoteTagsCallCount()).To(Equal(1)) 211 registries, repo := fakeDockerSession.GetRemoteTagsArgsForCall(0) 212 Expect(registries).To(ConsistOf("http://my.custom.registry:5000/v1/")) 213 Expect(repo).To(Equal("savory-app")) 214 215 Expect(fakeDockerSession.GetRemoteImageJSONCallCount()).To(Equal(1)) 216 imgIDParam, remoteImageEndpointParam := fakeDockerSession.GetRemoteImageJSONArgsForCall(0) 217 Expect(imgIDParam).To(Equal("29d531509fb")) 218 Expect(remoteImageEndpointParam).To(Equal("http://my.custom.registry:5000/v1/")) 219 }) 220 221 Context("when getting another error after retrying", func() { 222 const insecureRegistryErrorMessage = "If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry 192.168.11.1:5000` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/192.168.11.1:5000/ca.crt" 223 224 It("returns the error", func() { 225 fakeDockerSessionFactory.MakeSessionReturns(fakeDockerSession, errors.New(insecureRegistryErrorMessage)) 226 227 dockerPath := "verybad/apple" 228 _, err := dockerMetadataFetcher.FetchMetadata(dockerPath) 229 Expect(err).To(MatchError(ContainSubstring("private registry supports only HTTP or HTTPS with an unknown CA certificate"))) 230 231 Expect(fakeDockerSessionFactory.MakeSessionCallCount()).To(Equal(2)) 232 233 reposName, allowInsecure := fakeDockerSessionFactory.MakeSessionArgsForCall(0) 234 Expect(reposName).To(Equal(dockerPath)) 235 Expect(allowInsecure).To(BeFalse()) 236 237 reposName, allowInsecure = fakeDockerSessionFactory.MakeSessionArgsForCall(1) 238 Expect(reposName).To(Equal(dockerPath)) 239 Expect(allowInsecure).To(BeTrue()) 240 }) 241 }) 242 }) 243 244 Context("when exposed ports are null in the docker metadata", func() { 245 It("doesn't blow up, and returns zero values", func() { 246 fakeDockerSessionFactory.MakeSessionReturns(fakeDockerSession, nil) 247 imageList := map[string]*registry.ImgData{ 248 "29d531509fb": ®istry.ImgData{ 249 ID: "29d531509fb", 250 Checksum: "dsflksdfjlkj", 251 ChecksumPayload: "sdflksdjfkl", 252 Tag: "latest", 253 }, 254 } 255 fakeDockerSession.GetRepositoryDataReturns( 256 ®istry.RepositoryData{ 257 ImgList: imageList, 258 Endpoints: []string{"https://registry-1.docker.io/v1/"}, 259 }, nil) 260 261 fakeDockerSession.GetRemoteTagsReturns(map[string]string{"latest": "29d531509fb"}, nil) 262 fakeDockerSession.GetRemoteImageJSONReturns( 263 []byte(`{ 264 "container_config":{ "ExposedPorts":null }, 265 "config":{ 266 "WorkingDir":"/home/app", 267 "User":"the-meta-user", 268 "Entrypoint":["/lattice-app"], 269 "Cmd":["--enableAwesomeMode=true","iloveargs"], 270 "Env":["A=1","B=2"] 271 } 272 }`), 273 0, 274 nil) 275 repoName := "cool_user123/sweetapp" 276 277 imageMetadata, err := dockerMetadataFetcher.FetchMetadata(repoName) 278 Expect(err).NotTo(HaveOccurred()) 279 Expect(imageMetadata).NotTo(BeNil()) 280 Expect(imageMetadata.ExposedPorts).To(BeEmpty()) 281 }) 282 }) 283 284 Context("when there is an error parsing the docker image reference", func() { 285 It("returns an error", func() { 286 _, err := dockerMetadataFetcher.FetchMetadata("bad/appName") 287 Expect(err).To(MatchError(ContainSubstring("repository name component must match"))) 288 }) 289 }) 290 291 Context("when there is an error making the session", func() { 292 It("returns an error", func() { 293 fakeDockerSessionFactory.MakeSessionReturns(fakeDockerSession, errors.New("Couldn't make a session.")) 294 295 _, err := dockerMetadataFetcher.FetchMetadata("verybad/apple") 296 Expect(err).To(MatchError("Couldn't make a session.")) 297 }) 298 }) 299 300 Context("when there is an error getting the repository data", func() { 301 It("returns an error", func() { 302 fakeDockerSessionFactory.MakeSessionReturns(fakeDockerSession, nil) 303 fakeDockerSession.GetRepositoryDataReturns(®istry.RepositoryData{}, errors.New("We floundered getting your repo data.")) 304 305 _, err := dockerMetadataFetcher.FetchMetadata("cloud_flounder/fishy") 306 Expect(err).To(MatchError("We floundered getting your repo data.")) 307 }) 308 }) 309 310 Context("when there is an error getting remote tags", func() { 311 It("returns an error", func() { 312 fakeDockerSessionFactory.MakeSessionReturns(fakeDockerSession, nil) 313 fakeDockerSession.GetRepositoryDataReturns( 314 ®istry.RepositoryData{ 315 ImgList: map[string]*registry.ImgData{}, 316 }, nil) 317 fakeDockerSession.GetRemoteTagsReturns(nil, errors.New("Can't get tags!")) 318 319 _, err := dockerMetadataFetcher.FetchMetadata("tagless/inseattle") 320 Expect(err).To(MatchError("Can't get tags!")) 321 }) 322 }) 323 324 Context("When the requested tag does not exist", func() { 325 It("returns an error", func() { 326 fakeDockerSessionFactory.MakeSessionReturns(fakeDockerSession, nil) 327 imageList := map[string]*registry.ImgData{ 328 "29d531509fb": ®istry.ImgData{ 329 ID: "29d531509fb", 330 Checksum: "dsflksdfjlkj", 331 ChecksumPayload: "sdflksdjfkl", 332 Tag: "latest", 333 }, 334 } 335 fakeDockerSession.GetRepositoryDataReturns( 336 ®istry.RepositoryData{ 337 ImgList: imageList, 338 Endpoints: []string{"https://registry-1.docker.io/v1/"}, 339 }, nil) 340 fakeDockerSession.GetRemoteTagsReturns(map[string]string{"latest": "29d531509fb"}, nil) 341 342 _, err := dockerMetadataFetcher.FetchMetadata("wiggle/app:some-unknown-tag-v3245") 343 Expect(err).To(MatchError("Unknown tag: wiggle/app:some-unknown-tag-v3245")) 344 }) 345 }) 346 347 Describe("Handling image JSON errors", func() { 348 BeforeEach(func() { 349 fakeDockerSessionFactory.MakeSessionReturns(fakeDockerSession, nil) 350 imageList := map[string]*registry.ImgData{ 351 "29d531509fb": ®istry.ImgData{ 352 ID: "29d531509fb", 353 Checksum: "dsflksdfjlkj", 354 ChecksumPayload: "sdflksdjfkl", 355 Tag: "latest", 356 }, 357 } 358 fakeDockerSession.GetRepositoryDataReturns( 359 ®istry.RepositoryData{ 360 ImgList: imageList, 361 Endpoints: []string{"https://registry-1.docker.io/v1/"}, 362 }, nil) 363 fakeDockerSession.GetRemoteTagsReturns(map[string]string{"latest": "29d531509fb"}, nil) 364 }) 365 366 Context("when there is an error getting the remote image json", func() { 367 It("returns an error", func() { 368 fakeDockerSession.GetRemoteImageJSONReturns([]byte{}, 0, errors.New("JSON? What's that!???")) 369 370 _, err := dockerMetadataFetcher.FetchMetadata("wiggle/app") 371 Expect(err).To(MatchError("JSON? What's that!???")) 372 }) 373 }) 374 375 Context("when there is an error parsing the remote image json", func() { 376 It("returns an error", func() { 377 fakeDockerSession.GetRemoteImageJSONReturns([]byte("i'm not valid json"), 0, nil) 378 379 _, err := dockerMetadataFetcher.FetchMetadata("wiggle/app") 380 Expect(err).To(MatchError("Error parsing remote image json for specified docker image:\ninvalid character 'i' looking for beginning of value")) 381 }) 382 }) 383 384 Context("When Config is missing from the image Json", func() { 385 It("returns an error", func() { 386 fakeDockerSession.GetRemoteImageJSONReturns([]byte("{}"), 0, nil) 387 388 _, err := dockerMetadataFetcher.FetchMetadata("wiggle/app") 389 Expect(err).To(MatchError("Parsing start command failed")) 390 }) 391 }) 392 }) 393 }) 394 })