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": &registry.ImgData{
    34  						ID:              "29d531509fb",
    35  						Checksum:        "dsflksdfjlkj",
    36  						ChecksumPayload: "sdflksdjfkl",
    37  						Tag:             "latest",
    38  					},
    39  				}
    40  				fakeDockerSession.GetRepositoryDataReturns(
    41  					&registry.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": &registry.ImgData{
    94  						ID:              "29d531509fb",
    95  						Checksum:        "dsflksdfjlkj",
    96  						ChecksumPayload: "sdflksdjfkl",
    97  						Tag:             "latest",
    98  					},
    99  				}
   100  				fakeDockerSession.GetRepositoryDataReturns(
   101  					&registry.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": &registry.ImgData{
   161  						ID:              "29d531509fb",
   162  						Checksum:        "dsflksdfjlkj",
   163  						ChecksumPayload: "sdflksdjfkl",
   164  						Tag:             "latest",
   165  					},
   166  				}
   167  				fakeDockerSession.GetRepositoryDataReturns(
   168  					&registry.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": &registry.ImgData{
   249  						ID:              "29d531509fb",
   250  						Checksum:        "dsflksdfjlkj",
   251  						ChecksumPayload: "sdflksdjfkl",
   252  						Tag:             "latest",
   253  					},
   254  				}
   255  				fakeDockerSession.GetRepositoryDataReturns(
   256  					&registry.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(&registry.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  					&registry.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": &registry.ImgData{
   329  						ID:              "29d531509fb",
   330  						Checksum:        "dsflksdfjlkj",
   331  						ChecksumPayload: "sdflksdjfkl",
   332  						Tag:             "latest",
   333  					},
   334  				}
   335  				fakeDockerSession.GetRepositoryDataReturns(
   336  					&registry.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": &registry.ImgData{
   352  						ID:              "29d531509fb",
   353  						Checksum:        "dsflksdfjlkj",
   354  						ChecksumPayload: "sdflksdjfkl",
   355  						Tag:             "latest",
   356  					},
   357  				}
   358  				fakeDockerSession.GetRepositoryDataReturns(
   359  					&registry.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  })