diff --git a/depot/containerstore/containerstore.go b/depot/containerstore/containerstore.go index 09a1afc2..53ebcb3d 100644 --- a/depot/containerstore/containerstore.go +++ b/depot/containerstore/containerstore.go @@ -87,6 +87,8 @@ type containerStore struct { enableUnproxiedPortMappings bool advertisePreferenceForInstanceAddress bool + volumeMountedFiles VolumeMountedFilesImplementor + jsonMarshaller func(any) ([]byte, error) } @@ -110,6 +112,7 @@ func New( cellID string, enableUnproxiedPortMappings bool, advertisePreferenceForInstanceAddress bool, + volumeMountedFiles VolumeMountedFilesImplementor, jsonMarshaller func(any) ([]byte, error), ) ContainerStore { return &containerStore{ @@ -134,6 +137,7 @@ func New( enableUnproxiedPortMappings: enableUnproxiedPortMappings, advertisePreferenceForInstanceAddress: advertisePreferenceForInstanceAddress, + volumeMountedFiles: volumeMountedFiles, jsonMarshaller: jsonMarshaller, } } @@ -170,6 +174,7 @@ func (cs *containerStore) Reserve(logger lager.Logger, traceID string, req *exec cs.cellID, cs.enableUnproxiedPortMappings, cs.advertisePreferenceForInstanceAddress, + cs.volumeMountedFiles, cs.jsonMarshaller, )) diff --git a/depot/containerstore/containerstore_test.go b/depot/containerstore/containerstore_test.go index fd4cf450..fde81994 100644 --- a/depot/containerstore/containerstore_test.go +++ b/depot/containerstore/containerstore_test.go @@ -71,10 +71,11 @@ var _ = Describe("Container Store", func() { logManager *containerstorefakes.FakeLogManager logStreamer *fake_log_streamer.FakeLogStreamer - clock *fakeclock.FakeClock - eventEmitter *eventfakes.FakeHub - metronClient *mfakes.FakeIngressClient - rootFSSizer *configurationfakes.FakeRootFSSizer + clock *fakeclock.FakeClock + eventEmitter *eventfakes.FakeHub + metronClient *mfakes.FakeIngressClient + rootFSSizer *configurationfakes.FakeRootFSSizer + volumeMountedFilesHandler *containerstorefakes.FakeVolumeMountedFilesImplementor ) var containerState = func(guid string) func() executor.State { @@ -113,6 +114,7 @@ var _ = Describe("Container Store", func() { clock = fakeclock.NewFakeClock(time.Now()) eventEmitter = &eventfakes.FakeHub{} rootFSSizer = new(configurationfakes.FakeRootFSSizer) + volumeMountedFilesHandler = &containerstorefakes.FakeVolumeMountedFilesImplementor{} credManager.RunnerReturns(ifrit.RunFunc(func(signals <-chan os.Signal, ready chan<- struct{}) error { close(ready) @@ -164,6 +166,7 @@ var _ = Describe("Container Store", func() { cellID, true, advertisePreferenceForInstanceAddress, + volumeMountedFilesHandler, json.Marshal, ) @@ -378,6 +381,10 @@ var _ = Describe("Container Store", func() { {Name: "beep", Value: "booop"}, } + volumeMountedFiles := []executor.VolumeMountedFiles{ + {Path: "/redis/username", Content: "redis_username"}, + } + logGuid = "log-guid-foo" runInfo = executor.RunInfo{ RootFSPath: "/foo/bar", @@ -406,6 +413,7 @@ var _ = Describe("Container Store", func() { }, EnableContainerProxy: true, LogRateLimitBytesPerSecond: logRateUnlimitedBytesPerSecond, + VolumeMountedFiles: volumeMountedFiles, } runReq = &executor.RunRequest{ @@ -489,6 +497,7 @@ var _ = Describe("Container Store", func() { cellID, true, advertisePreferenceForInstanceAddress, + volumeMountedFilesHandler, json.Marshal, ) }) @@ -645,6 +654,26 @@ var _ = Describe("Container Store", func() { Expect(containerSpec.Env).To(Equal(expectedEnv)) }) + It("creates container with service binding root", func() { + expectedMount := garden.BindMount{ + SrcPath: "/var/vcap/data/rep/shared/garden/service_binding_root", + DstPath: "/etc/cf-instance-binding", + Mode: garden.BindMountModeRO, + Origin: garden.BindMountOriginHost, + } + + dependencyManager.DownloadCachedDependenciesReturns(containerstore.BindMounts{ + GardenBindMounts: []garden.BindMount{expectedMount}, + }, nil) + + _, err := containerStore.Create(logger, "some-trace-id", containerGuid) + Expect(err).NotTo(HaveOccurred()) + + Expect(gardenClient.CreateCallCount()).To(Equal(1)) + containerSpec := gardenClient.CreateArgsForCall(0) + Expect(containerSpec.BindMounts).To(ContainElement(expectedMount)) + }) + It("sets the correct external and internal ip", func() { container, err := containerStore.Create(logger, "some-trace-id", containerGuid) Expect(err).NotTo(HaveOccurred()) @@ -713,6 +742,7 @@ var _ = Describe("Container Store", func() { cellID, true, advertisePreferenceForInstanceAddress, + volumeMountedFilesHandler, json.Marshal, ) }) @@ -1254,6 +1284,7 @@ var _ = Describe("Container Store", func() { cellID, true, advertisePreferenceForInstanceAddress, + volumeMountedFilesHandler, json.Marshal, ) @@ -1344,6 +1375,7 @@ var _ = Describe("Container Store", func() { cellID, false, advertisePreferenceForInstanceAddress, + volumeMountedFilesHandler, json.Marshal, ) }) @@ -2493,6 +2525,7 @@ var _ = Describe("Container Store", func() { cellID, true, advertisePreferenceForInstanceAddress, + volumeMountedFilesHandler, fm.Marshal, ) }) @@ -3066,6 +3099,7 @@ var _ = Describe("Container Store", func() { cellID, true, advertisePreferenceForInstanceAddress, + volumeMountedFilesHandler, json.Marshal, ) diff --git a/depot/containerstore/containerstorefakes/fake_fs_operations.go b/depot/containerstore/containerstorefakes/fake_fs_operations.go new file mode 100644 index 00000000..efbf875e --- /dev/null +++ b/depot/containerstore/containerstorefakes/fake_fs_operations.go @@ -0,0 +1,420 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package containerstorefakes + +import ( + "os" + "sync" + + "code.cloudfoundry.org/executor/depot/containerstore" +) + +type FakeFSOperator struct { + ChdirStub func(string) error + chdirMutex sync.RWMutex + chdirArgsForCall []struct { + arg1 string + } + chdirReturns struct { + result1 error + } + chdirReturnsOnCall map[int]struct { + result1 error + } + CreateFileStub func(string) (*os.File, error) + createFileMutex sync.RWMutex + createFileArgsForCall []struct { + arg1 string + } + createFileReturns struct { + result1 *os.File + result2 error + } + createFileReturnsOnCall map[int]struct { + result1 *os.File + result2 error + } + MkdirAllStub func(string) error + mkdirAllMutex sync.RWMutex + mkdirAllArgsForCall []struct { + arg1 string + } + mkdirAllReturns struct { + result1 error + } + mkdirAllReturnsOnCall map[int]struct { + result1 error + } + RemoveAllStub func(string) error + removeAllMutex sync.RWMutex + removeAllArgsForCall []struct { + arg1 string + } + removeAllReturns struct { + result1 error + } + removeAllReturnsOnCall map[int]struct { + result1 error + } + WriteFileStub func(string, []byte) error + writeFileMutex sync.RWMutex + writeFileArgsForCall []struct { + arg1 string + arg2 []byte + } + writeFileReturns struct { + result1 error + } + writeFileReturnsOnCall map[int]struct { + result1 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeFSOperator) Chdir(arg1 string) error { + fake.chdirMutex.Lock() + ret, specificReturn := fake.chdirReturnsOnCall[len(fake.chdirArgsForCall)] + fake.chdirArgsForCall = append(fake.chdirArgsForCall, struct { + arg1 string + }{arg1}) + stub := fake.ChdirStub + fakeReturns := fake.chdirReturns + fake.recordInvocation("Chdir", []interface{}{arg1}) + fake.chdirMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeFSOperator) ChdirCallCount() int { + fake.chdirMutex.RLock() + defer fake.chdirMutex.RUnlock() + return len(fake.chdirArgsForCall) +} + +func (fake *FakeFSOperator) ChdirCalls(stub func(string) error) { + fake.chdirMutex.Lock() + defer fake.chdirMutex.Unlock() + fake.ChdirStub = stub +} + +func (fake *FakeFSOperator) ChdirArgsForCall(i int) string { + fake.chdirMutex.RLock() + defer fake.chdirMutex.RUnlock() + argsForCall := fake.chdirArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeFSOperator) ChdirReturns(result1 error) { + fake.chdirMutex.Lock() + defer fake.chdirMutex.Unlock() + fake.ChdirStub = nil + fake.chdirReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeFSOperator) ChdirReturnsOnCall(i int, result1 error) { + fake.chdirMutex.Lock() + defer fake.chdirMutex.Unlock() + fake.ChdirStub = nil + if fake.chdirReturnsOnCall == nil { + fake.chdirReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.chdirReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *FakeFSOperator) CreateFile(arg1 string) (*os.File, error) { + fake.createFileMutex.Lock() + ret, specificReturn := fake.createFileReturnsOnCall[len(fake.createFileArgsForCall)] + fake.createFileArgsForCall = append(fake.createFileArgsForCall, struct { + arg1 string + }{arg1}) + stub := fake.CreateFileStub + fakeReturns := fake.createFileReturns + fake.recordInvocation("CreateFile", []interface{}{arg1}) + fake.createFileMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeFSOperator) CreateFileCallCount() int { + fake.createFileMutex.RLock() + defer fake.createFileMutex.RUnlock() + return len(fake.createFileArgsForCall) +} + +func (fake *FakeFSOperator) CreateFileCalls(stub func(string) (*os.File, error)) { + fake.createFileMutex.Lock() + defer fake.createFileMutex.Unlock() + fake.CreateFileStub = stub +} + +func (fake *FakeFSOperator) CreateFileArgsForCall(i int) string { + fake.createFileMutex.RLock() + defer fake.createFileMutex.RUnlock() + argsForCall := fake.createFileArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeFSOperator) CreateFileReturns(result1 *os.File, result2 error) { + fake.createFileMutex.Lock() + defer fake.createFileMutex.Unlock() + fake.CreateFileStub = nil + fake.createFileReturns = struct { + result1 *os.File + result2 error + }{result1, result2} +} + +func (fake *FakeFSOperator) CreateFileReturnsOnCall(i int, result1 *os.File, result2 error) { + fake.createFileMutex.Lock() + defer fake.createFileMutex.Unlock() + fake.CreateFileStub = nil + if fake.createFileReturnsOnCall == nil { + fake.createFileReturnsOnCall = make(map[int]struct { + result1 *os.File + result2 error + }) + } + fake.createFileReturnsOnCall[i] = struct { + result1 *os.File + result2 error + }{result1, result2} +} + +func (fake *FakeFSOperator) MkdirAll(arg1 string) error { + fake.mkdirAllMutex.Lock() + ret, specificReturn := fake.mkdirAllReturnsOnCall[len(fake.mkdirAllArgsForCall)] + fake.mkdirAllArgsForCall = append(fake.mkdirAllArgsForCall, struct { + arg1 string + }{arg1}) + stub := fake.MkdirAllStub + fakeReturns := fake.mkdirAllReturns + fake.recordInvocation("MkdirAll", []interface{}{arg1}) + fake.mkdirAllMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeFSOperator) MkdirAllCallCount() int { + fake.mkdirAllMutex.RLock() + defer fake.mkdirAllMutex.RUnlock() + return len(fake.mkdirAllArgsForCall) +} + +func (fake *FakeFSOperator) MkdirAllCalls(stub func(string) error) { + fake.mkdirAllMutex.Lock() + defer fake.mkdirAllMutex.Unlock() + fake.MkdirAllStub = stub +} + +func (fake *FakeFSOperator) MkdirAllArgsForCall(i int) string { + fake.mkdirAllMutex.RLock() + defer fake.mkdirAllMutex.RUnlock() + argsForCall := fake.mkdirAllArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeFSOperator) MkdirAllReturns(result1 error) { + fake.mkdirAllMutex.Lock() + defer fake.mkdirAllMutex.Unlock() + fake.MkdirAllStub = nil + fake.mkdirAllReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeFSOperator) MkdirAllReturnsOnCall(i int, result1 error) { + fake.mkdirAllMutex.Lock() + defer fake.mkdirAllMutex.Unlock() + fake.MkdirAllStub = nil + if fake.mkdirAllReturnsOnCall == nil { + fake.mkdirAllReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.mkdirAllReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *FakeFSOperator) RemoveAll(arg1 string) error { + fake.removeAllMutex.Lock() + ret, specificReturn := fake.removeAllReturnsOnCall[len(fake.removeAllArgsForCall)] + fake.removeAllArgsForCall = append(fake.removeAllArgsForCall, struct { + arg1 string + }{arg1}) + stub := fake.RemoveAllStub + fakeReturns := fake.removeAllReturns + fake.recordInvocation("RemoveAll", []interface{}{arg1}) + fake.removeAllMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeFSOperator) RemoveAllCallCount() int { + fake.removeAllMutex.RLock() + defer fake.removeAllMutex.RUnlock() + return len(fake.removeAllArgsForCall) +} + +func (fake *FakeFSOperator) RemoveAllCalls(stub func(string) error) { + fake.removeAllMutex.Lock() + defer fake.removeAllMutex.Unlock() + fake.RemoveAllStub = stub +} + +func (fake *FakeFSOperator) RemoveAllArgsForCall(i int) string { + fake.removeAllMutex.RLock() + defer fake.removeAllMutex.RUnlock() + argsForCall := fake.removeAllArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeFSOperator) RemoveAllReturns(result1 error) { + fake.removeAllMutex.Lock() + defer fake.removeAllMutex.Unlock() + fake.RemoveAllStub = nil + fake.removeAllReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeFSOperator) RemoveAllReturnsOnCall(i int, result1 error) { + fake.removeAllMutex.Lock() + defer fake.removeAllMutex.Unlock() + fake.RemoveAllStub = nil + if fake.removeAllReturnsOnCall == nil { + fake.removeAllReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.removeAllReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *FakeFSOperator) WriteFile(arg1 string, arg2 []byte) error { + var arg2Copy []byte + if arg2 != nil { + arg2Copy = make([]byte, len(arg2)) + copy(arg2Copy, arg2) + } + fake.writeFileMutex.Lock() + ret, specificReturn := fake.writeFileReturnsOnCall[len(fake.writeFileArgsForCall)] + fake.writeFileArgsForCall = append(fake.writeFileArgsForCall, struct { + arg1 string + arg2 []byte + }{arg1, arg2Copy}) + stub := fake.WriteFileStub + fakeReturns := fake.writeFileReturns + fake.recordInvocation("WriteFile", []interface{}{arg1, arg2Copy}) + fake.writeFileMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeFSOperator) WriteFileCallCount() int { + fake.writeFileMutex.RLock() + defer fake.writeFileMutex.RUnlock() + return len(fake.writeFileArgsForCall) +} + +func (fake *FakeFSOperator) WriteFileCalls(stub func(string, []byte) error) { + fake.writeFileMutex.Lock() + defer fake.writeFileMutex.Unlock() + fake.WriteFileStub = stub +} + +func (fake *FakeFSOperator) WriteFileArgsForCall(i int) (string, []byte) { + fake.writeFileMutex.RLock() + defer fake.writeFileMutex.RUnlock() + argsForCall := fake.writeFileArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeFSOperator) WriteFileReturns(result1 error) { + fake.writeFileMutex.Lock() + defer fake.writeFileMutex.Unlock() + fake.WriteFileStub = nil + fake.writeFileReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeFSOperator) WriteFileReturnsOnCall(i int, result1 error) { + fake.writeFileMutex.Lock() + defer fake.writeFileMutex.Unlock() + fake.WriteFileStub = nil + if fake.writeFileReturnsOnCall == nil { + fake.writeFileReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.writeFileReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *FakeFSOperator) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.chdirMutex.RLock() + defer fake.chdirMutex.RUnlock() + fake.createFileMutex.RLock() + defer fake.createFileMutex.RUnlock() + fake.mkdirAllMutex.RLock() + defer fake.mkdirAllMutex.RUnlock() + fake.removeAllMutex.RLock() + defer fake.removeAllMutex.RUnlock() + fake.writeFileMutex.RLock() + defer fake.writeFileMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeFSOperator) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ containerstore.FSOperator = new(FakeFSOperator) diff --git a/depot/containerstore/containerstorefakes/fake_volume_mounted_files_handler.go b/depot/containerstore/containerstorefakes/fake_volume_mounted_files_handler.go new file mode 100644 index 00000000..e4f0ca79 --- /dev/null +++ b/depot/containerstore/containerstorefakes/fake_volume_mounted_files_handler.go @@ -0,0 +1,197 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package containerstorefakes + +import ( + "sync" + + "code.cloudfoundry.org/executor" + "code.cloudfoundry.org/executor/depot/containerstore" + "code.cloudfoundry.org/garden" + lager "code.cloudfoundry.org/lager/v3" +) + +type FakeVolumeMountedFilesImplementor struct { + CreateDirStub func(lager.Logger, executor.Container) ([]garden.BindMount, error) + createDirMutex sync.RWMutex + createDirArgsForCall []struct { + arg1 lager.Logger + arg2 executor.Container + } + createDirReturns struct { + result1 []garden.BindMount + result2 error + } + createDirReturnsOnCall map[int]struct { + result1 []garden.BindMount + result2 error + } + RemoveDirStub func(lager.Logger, executor.Container) error + removeDirMutex sync.RWMutex + removeDirArgsForCall []struct { + arg1 lager.Logger + arg2 executor.Container + } + removeDirReturns struct { + result1 error + } + removeDirReturnsOnCall map[int]struct { + result1 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeVolumeMountedFilesImplementor) CreateDir(arg1 lager.Logger, arg2 executor.Container) ([]garden.BindMount, error) { + fake.createDirMutex.Lock() + ret, specificReturn := fake.createDirReturnsOnCall[len(fake.createDirArgsForCall)] + fake.createDirArgsForCall = append(fake.createDirArgsForCall, struct { + arg1 lager.Logger + arg2 executor.Container + }{arg1, arg2}) + stub := fake.CreateDirStub + fakeReturns := fake.createDirReturns + fake.recordInvocation("CreateDir", []interface{}{arg1, arg2}) + fake.createDirMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeVolumeMountedFilesImplementor) CreateDirCallCount() int { + fake.createDirMutex.RLock() + defer fake.createDirMutex.RUnlock() + return len(fake.createDirArgsForCall) +} + +func (fake *FakeVolumeMountedFilesImplementor) CreateDirCalls(stub func(lager.Logger, executor.Container) ([]garden.BindMount, error)) { + fake.createDirMutex.Lock() + defer fake.createDirMutex.Unlock() + fake.CreateDirStub = stub +} + +func (fake *FakeVolumeMountedFilesImplementor) CreateDirArgsForCall(i int) (lager.Logger, executor.Container) { + fake.createDirMutex.RLock() + defer fake.createDirMutex.RUnlock() + argsForCall := fake.createDirArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeVolumeMountedFilesImplementor) CreateDirReturns(result1 []garden.BindMount, result2 error) { + fake.createDirMutex.Lock() + defer fake.createDirMutex.Unlock() + fake.CreateDirStub = nil + fake.createDirReturns = struct { + result1 []garden.BindMount + result2 error + }{result1, result2} +} + +func (fake *FakeVolumeMountedFilesImplementor) CreateDirReturnsOnCall(i int, result1 []garden.BindMount, result2 error) { + fake.createDirMutex.Lock() + defer fake.createDirMutex.Unlock() + fake.CreateDirStub = nil + if fake.createDirReturnsOnCall == nil { + fake.createDirReturnsOnCall = make(map[int]struct { + result1 []garden.BindMount + result2 error + }) + } + fake.createDirReturnsOnCall[i] = struct { + result1 []garden.BindMount + result2 error + }{result1, result2} +} + +func (fake *FakeVolumeMountedFilesImplementor) RemoveDir(arg1 lager.Logger, arg2 executor.Container) error { + fake.removeDirMutex.Lock() + ret, specificReturn := fake.removeDirReturnsOnCall[len(fake.removeDirArgsForCall)] + fake.removeDirArgsForCall = append(fake.removeDirArgsForCall, struct { + arg1 lager.Logger + arg2 executor.Container + }{arg1, arg2}) + stub := fake.RemoveDirStub + fakeReturns := fake.removeDirReturns + fake.recordInvocation("RemoveDir", []interface{}{arg1, arg2}) + fake.removeDirMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeVolumeMountedFilesImplementor) RemoveDirCallCount() int { + fake.removeDirMutex.RLock() + defer fake.removeDirMutex.RUnlock() + return len(fake.removeDirArgsForCall) +} + +func (fake *FakeVolumeMountedFilesImplementor) RemoveDirCalls(stub func(lager.Logger, executor.Container) error) { + fake.removeDirMutex.Lock() + defer fake.removeDirMutex.Unlock() + fake.RemoveDirStub = stub +} + +func (fake *FakeVolumeMountedFilesImplementor) RemoveDirArgsForCall(i int) (lager.Logger, executor.Container) { + fake.removeDirMutex.RLock() + defer fake.removeDirMutex.RUnlock() + argsForCall := fake.removeDirArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeVolumeMountedFilesImplementor) RemoveDirReturns(result1 error) { + fake.removeDirMutex.Lock() + defer fake.removeDirMutex.Unlock() + fake.RemoveDirStub = nil + fake.removeDirReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeVolumeMountedFilesImplementor) RemoveDirReturnsOnCall(i int, result1 error) { + fake.removeDirMutex.Lock() + defer fake.removeDirMutex.Unlock() + fake.RemoveDirStub = nil + if fake.removeDirReturnsOnCall == nil { + fake.removeDirReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.removeDirReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *FakeVolumeMountedFilesImplementor) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.createDirMutex.RLock() + defer fake.createDirMutex.RUnlock() + fake.removeDirMutex.RLock() + defer fake.removeDirMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeVolumeMountedFilesImplementor) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ containerstore.VolumeMountedFilesImplementor = new(FakeVolumeMountedFilesImplementor) diff --git a/depot/containerstore/storenode.go b/depot/containerstore/storenode.go index 06030cb8..4607999d 100644 --- a/depot/containerstore/storenode.go +++ b/depot/containerstore/storenode.go @@ -33,6 +33,7 @@ const ContainerMissingMessage = "missing garden container" const VolmanMountFailed = "failed to mount volume" const BindMountCleanupFailed = "failed to cleanup bindmount artifacts" const CredDirFailed = "failed to create credentials directory" +const VolumeMountedFileFailed = "failed to create volume mounted files" const ContainerCompletedCount = "ContainerCompletedCount" const ContainerExitedOnTimeoutCount = "ContainerExitedOnTimeoutCount" @@ -92,6 +93,8 @@ type storeNode struct { startTime time.Time regenerateCertsCh chan struct{} + volumeMountedFiles VolumeMountedFilesImplementor + jsonMarshaller func(any) ([]byte, error) } @@ -116,6 +119,7 @@ func newStoreNode( cellID string, enableUnproxiedPortMappings bool, advertisePreferenceForInstanceAddress bool, + volumeMountedFiles VolumeMountedFilesImplementor, jsonMarshaller func(any) ([]byte, error), ) *storeNode { return &storeNode{ @@ -143,6 +147,7 @@ func newStoreNode( enableUnproxiedPortMappings: enableUnproxiedPortMappings, advertisePreferenceForInstanceAddress: advertisePreferenceForInstanceAddress, regenerateCertsCh: make(chan struct{}, 1), + volumeMountedFiles: volumeMountedFiles, jsonMarshaller: jsonMarshaller, } } @@ -242,7 +247,19 @@ func (n *storeNode) Create(logger lager.Logger, traceID string) error { n.complete(logger, traceID, true, CredDirFailed, true) return err } + + if len(info.VolumeMountedFiles) > 0 { + volumeMountedFile, err := n.volumeMountedFiles.CreateDir(logger, info) + if err != nil { + n.complete(logger, traceID, true, VolumeMountedFileFailed, true) + return err + } + + n.bindMounts = append(n.bindMounts, volumeMountedFile...) + } + n.bindMounts = append(n.bindMounts, credMounts...) + info.Env = append(info.Env, envs...) if n.useDeclarativeHealthCheck { @@ -763,6 +780,7 @@ func (n *storeNode) Destroy(logger lager.Logger, traceID string) error { // ensure these directories are removed even if the container fails to destroy defer n.removeCredsDir(logger, info) defer n.umountVolumeMounts(logger, info) + defer n.removeVolumeMountedFiles(logger, info) err := n.destroyContainer(logger, traceID) if err != nil { @@ -873,6 +891,13 @@ func (n *storeNode) removeCredsDir(logger lager.Logger, info executor.Container) } } +func (n *storeNode) removeVolumeMountedFiles(logger lager.Logger, info executor.Container) { + err := n.volumeMountedFiles.RemoveDir(logger, info) + if err != nil { + logger.Error("failed-to-delete-volume-mounted-files-config-dir", err) + } +} + func (n *storeNode) umountVolumeMounts(logger lager.Logger, info executor.Container) { for _, volume := range info.VolumeMounts { err := n.volumeManager.Unmount(logger, volume.Driver, volume.VolumeId, info.Guid) diff --git a/depot/containerstore/volume_mounted_files_handler.go b/depot/containerstore/volume_mounted_files_handler.go new file mode 100644 index 00000000..1ff599a0 --- /dev/null +++ b/depot/containerstore/volume_mounted_files_handler.go @@ -0,0 +1,172 @@ +package containerstore + +import ( + "fmt" + "os" + "path/filepath" + + "code.cloudfoundry.org/executor" + "code.cloudfoundry.org/garden" + "code.cloudfoundry.org/lager/v3" +) + +//go:generate counterfeiter -o containerstorefakes/fake_volume_mounted_files_handler.go . VolumeMountedFilesImplementor +type VolumeMountedFilesImplementor interface { + CreateDir(logger lager.Logger, container executor.Container) ([]garden.BindMount, error) + RemoveDir(logger lager.Logger, container executor.Container) error +} + +type VolumeMountedFilesHandler struct { + fsOperations FSOperator + containerMountPath string + volumeMountPath string +} + +func NewVolumeMountedFilesHandler( + fsOperations FSOperator, + volumeMountPath string, + containerMountPath string, +) *VolumeMountedFilesHandler { + return &VolumeMountedFilesHandler{ + fsOperations: fsOperations, + volumeMountPath: volumeMountPath, + containerMountPath: containerMountPath, + } +} + +func (h *VolumeMountedFilesHandler) CreateDir(logger lager.Logger, container executor.Container) ([]garden.BindMount, error) { + err := h.fsOperations.Chdir(h.volumeMountPath) + if err != nil { + return nil, fmt.Errorf("volume mount path doesn't exists %s", err.Error()) + } + + containerDir := filepath.Join(h.volumeMountPath, container.Guid) + + err = h.fsOperations.MkdirAll(container.Guid) + if err != nil { + return nil, err + } + + logger.Info(fmt.Sprintf("creating container dir: %s", containerDir)) + + errServiceBinding := h.volumeMountedFilesForServices(logger, containerDir, container) + if errServiceBinding != nil { + logger.Error("creating-dir-volume-mounted-files-failed", errServiceBinding) + return nil, errServiceBinding + } + + return []garden.BindMount{ + { + SrcPath: containerDir, + DstPath: h.containerMountPath, + Mode: garden.BindMountModeRO, + Origin: garden.BindMountOriginHost, + }, + }, nil +} + +func (h *VolumeMountedFilesHandler) volumeMountedFilesForServices( + logger lager.Logger, + containerDir string, + containers executor.Container, +) error { + var cleanupFiles []*os.File + + for _, roots := range containers.RunInfo.VolumeMountedFiles { + dirName := filepath.Dir(roots.Path) // if path is empty the dirName will be "." + fileName := filepath.Base(roots.Path) // if filename is empty the fileName will be "." + + if dirName == "." && fileName == "." { + err := fmt.Errorf("failed to extract volume-mounted-files directory. format is: /serviceName/fileName") + logger.Error(" volume-mounted-files directory is required", err, lager.Data{"dirName": "is empty"}) + + return err + } + + dirName = filepath.Join(containerDir, dirName) + + err := h.fsOperations.MkdirAll(dirName) + if err != nil { + logger.Error("failed-to-create-directory", err, lager.Data{"dirName": dirName}) + return fmt.Errorf("failed to create directory %s: %w", dirName, err) + } + + filePath := filepath.Join(dirName, fileName) + + serviceFile, err := h.fsOperations.CreateFile(filePath) + if err != nil { + logger.Error("failed-to-create-file", err, lager.Data{"filePath": filePath}) + return fmt.Errorf("failed to create file %s: %w", filePath, err) + } + + cleanupFiles = append(cleanupFiles, serviceFile) + + err = h.fsOperations.WriteFile(filePath, []byte(roots.Content)) + if err != nil { + logger.Error("failed-to-write-to-file", err, lager.Data{"filePath": filePath}) + + return fmt.Errorf("failed to write to file %s: %w", filePath, err) + } + } + + for _, file := range cleanupFiles { + err := file.Close() + if err != nil { + logger.Error("failed-to-close-file", err, lager.Data{"filePath": file.Name()}) + return fmt.Errorf("failed to close file %s: %w", file.Name(), err) + } + } + + return nil +} + +func (h *VolumeMountedFilesHandler) RemoveDir(logger lager.Logger, container executor.Container) error { + path := filepath.Join(h.volumeMountPath, container.Guid) + + err := h.fsOperations.RemoveAll(path) + if err != nil { + errMsg := "failed-to-remove-volume-mounted-files-directory" + logger.Error(errMsg, err, lager.Data{"directory": path}) + + err = fmt.Errorf("%s, %s", errMsg, err.Error()) + } + + return err +} + +//go:generate counterfeiter -o containerstorefakes/fake_fs_operations.go . FSOperator +type FSOperator interface { + CreateFile(filePath string) (*os.File, error) + WriteFile(name string, data []byte) error + MkdirAll(dirName string) error + RemoveAll(path string) error + Chdir(dir string) error +} + +type FSOperations struct{} + +var _ FSOperator = (*FSOperations)(nil) + +func NewFSOperations() *FSOperations { + return &FSOperations{} +} + +func (f FSOperations) CreateFile(filePath string) (*os.File, error) { + return os.Create(filePath) +} + +func (f FSOperations) WriteFile(name string, data []byte) error { + return os.WriteFile(name, data, 0644) +} + +func (f FSOperations) MkdirAll(dirName string) error { + return os.MkdirAll(dirName, 0755) +} + +func (f FSOperations) RemoveAll(path string) error { + return os.RemoveAll(path) +} + +func (f FSOperations) Chdir(path string) error { + return os.Chdir(path) +} diff --git a/depot/containerstore/volume_mounted_files_handler_test.go b/depot/containerstore/volume_mounted_files_handler_test.go new file mode 100644 index 00000000..0ead9de6 --- /dev/null +++ b/depot/containerstore/volume_mounted_files_handler_test.go @@ -0,0 +1,266 @@ +package containerstore_test + +import ( + "errors" + "fmt" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "os" + "path/filepath" + + "code.cloudfoundry.org/executor" + "code.cloudfoundry.org/executor/depot/containerstore" + "code.cloudfoundry.org/executor/depot/containerstore/containerstorefakes" + "code.cloudfoundry.org/garden" +) + +var _ = Describe("VolumeMountedFilesHandler", func() { + var ( + tmpdir string + fakeContainerUUID string + + handler *containerstore.VolumeMountedFilesHandler + fakeHandler *containerstore.VolumeMountedFilesHandler + container executor.Container + + fakeFSOperations *containerstorefakes.FakeFSOperator + ) + + AfterEach(func() { + os.RemoveAll(tmpdir) + }) + + BeforeEach(func() { + fakeContainerUUID = "E62613F8-7E85-4F49-B3EF-690BD2AE7EF2" + + tmpdir = filepath.Join(os.TempDir(), "volume_mounted_files") + err := os.MkdirAll(tmpdir, os.ModePerm) + Expect(err).NotTo(HaveOccurred()) + + container = executor.Container{Guid: fakeContainerUUID} + + container.VolumeMountedFiles = append(container.VolumeMountedFiles, executor.VolumeMountedFiles{ + Path: "/redis/username", Content: "username", + }) + + container.VolumeMountedFiles = append(container.VolumeMountedFiles, executor.VolumeMountedFiles{ + Path: "/redis/password", Content: "password", + }) + + container.VolumeMountedFiles = append(container.VolumeMountedFiles, executor.VolumeMountedFiles{ + Path: "/httpd/password", Content: "password", + }) + + container.VolumeMountedFiles = append(container.VolumeMountedFiles, executor.VolumeMountedFiles{ + Path: "/httpd/username", Content: "username", + }) + + container.VolumeMountedFiles = append(container.VolumeMountedFiles, executor.VolumeMountedFiles{ + Path: "/kafka/domain.com/password", Content: "password", + }) + + container.VolumeMountedFiles = append(container.VolumeMountedFiles, executor.VolumeMountedFiles{ + Path: "/kafka/domain.com/username", Content: "username", + }) + + container.VolumeMountedFiles = append(container.VolumeMountedFiles, executor.VolumeMountedFiles{ + Path: "/kafka/domain.org/username", Content: "username", + }) + + container.VolumeMountedFiles = append(container.VolumeMountedFiles, executor.VolumeMountedFiles{ + Path: "/kafka/domain.org/password", Content: "password", + }) + + handler = containerstore.NewVolumeMountedFilesHandler( + containerstore.NewFSOperations(), + tmpdir, + fakeContainerUUID, + ) + + fakeFSOperations = &containerstorefakes.FakeFSOperator{} + fakeHandler = containerstore.NewVolumeMountedFilesHandler( + fakeFSOperations, + tmpdir, + fakeContainerUUID, + ) + }) + + Context("CreateDir", func() { + It("returns a valid bind mount", func() { + + mount, err := handler.CreateDir(logger, container) + Expect(err).To(Succeed()) + + Expect(mount).To(HaveLen(1)) + Expect(mount[0].SrcPath).To(BeADirectory()) + Expect(mount[0].DstPath).To(Equal(fakeContainerUUID)) + Expect(mount[0].Mode).To(Equal(garden.BindMountModeRO)) + Expect(mount[0].Origin).To(Equal(garden.BindMountOriginHost)) + }) + + It("returns a valid volume mounted files configuration directory", func() { + _, err := handler.CreateDir(logger, container) + Expect(err).To(Succeed()) + + Expect(filepath.Join(tmpdir, fakeContainerUUID, "redis")).To(BeADirectory()) + + Expect(filepath.Join(tmpdir, fakeContainerUUID, "redis", "username")).To(BeAnExistingFile()) + Expect(filepath.Join(tmpdir, fakeContainerUUID, "redis", "username")).To(BeARegularFile()) + + Expect(filepath.Join(tmpdir, fakeContainerUUID, "redis", "password")).To(BeAnExistingFile()) + Expect(filepath.Join(tmpdir, fakeContainerUUID, "redis", "password")).To(BeARegularFile()) + + Expect(filepath.Join(tmpdir, fakeContainerUUID, "httpd", "username")).To(BeAnExistingFile()) + Expect(filepath.Join(tmpdir, fakeContainerUUID, "httpd", "username")).To(BeARegularFile()) + + Expect(filepath.Join(tmpdir, fakeContainerUUID, "httpd", "password")).To(BeAnExistingFile()) + Expect(filepath.Join(tmpdir, fakeContainerUUID, "httpd", "password")).To(BeARegularFile()) + }) + + It("when CreateDir/Chdir return an error", func() { + fakeFSOperations.ChdirReturns(&os.PathError{Op: "chdir", Err: errors.New("directory doesn't exist")}) + _, err := fakeHandler.CreateDir(logger, container) + + Expect(err).To(HaveOccurred()) + + var pathErr *os.PathError + Expect(errors.As(err, &pathErr)).To(BeTrue()) + + }) + + It("when CreateDir/Mkdir return an error", func() { + container.Guid = "" + fakeFSOperations.MkdirAllReturns(&os.PathError{Op: "mkdir", Err: errors.New("directory exists")}) + + _, err := fakeHandler.CreateDir(logger, container) + Expect(err).To(HaveOccurred()) + + var pathErr *os.PathError + Expect(errors.As(err, &pathErr)).To(BeTrue()) + + }) + + It("when CreateDir/volumeMountedFilesForServices dirName/fileName return an error", func() { + container.VolumeMountedFiles = []executor.VolumeMountedFiles{{ + Path: "", + Content: "content", + }} + + _, err := handler.CreateDir(logger, container) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("failed to extract volume-mounted-files directory. format is: /serviceName/fileName")) + }) + + Context("VolumeMountedFilesHandler Error Cases", func() { + type testCase struct { + description string + setupMock func() + expectedError string + } + + var testCases = []testCase{ + { + description: "fails when os.WriteFile returns an error due to file being non-writable", + setupMock: func() { + fakeFSOperations.WriteFileReturns(errors.New("permission denied")) + }, + expectedError: "permission denied", + }, + { + description: "fails when os.Create returns an error due to file being non-writable", + setupMock: func() { + fakeFSOperations.CreateFileReturns(nil, errors.New("permission denied")) + }, + expectedError: "permission denied", + }, + { + description: "when CreateDir/volumeMountedFilesForServices MkdirAll returns an error", + setupMock: func() { + fakeFSOperations.MkdirAllReturns(errors.New("file name too long")) + }, + expectedError: "file name too long", + }, + } + + for _, tc := range testCases { + tc := tc // capture range variable + It(tc.description, func() { + tc.setupMock() + + _, err := fakeHandler.CreateDir(logger, container) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(tc.expectedError)) + }) + } + }) + + Context("File Content Validation", func() { + testCases := []struct { + service string + fileName string + fileContent string + }{ + {"redis", "username", "username"}, + {"redis", "password", "password"}, + {"httpd", "username", "username"}, + {"httpd", "password", "password"}, + } + + for _, tc := range testCases { + service := tc.service + fileType := tc.fileName + expected := tc.fileContent + + It(fmt.Sprintf("validates the content of the %s %s file", service, fileType), func() { + _, err := handler.CreateDir(logger, container) + Expect(err).To(Succeed()) + + filePath := filepath.Join(tmpdir, fakeContainerUUID, service, fileType) + Expect(filePath).To(BeAnExistingFile()) + + content, err := os.ReadFile(filePath) + Expect(err).NotTo(HaveOccurred()) + Expect(string(content)).To(Equal(expected)) + }) + } + }) + }) + + Context("when volume mounted files directory doesn't exist", func() { + BeforeEach(func() { + handler = containerstore.NewVolumeMountedFilesHandler( + containerstore.NewFSOperations(), + "/some/fake/path", + "mount_path", + ) + }) + + It("when trying to change directory to volume mount fail", func() { + _, err := handler.CreateDir(logger, container) + Expect(err).To(HaveOccurred()) + + Expect(err.Error()).To(ContainSubstring("volume mount path doesn't exists")) + + }) + }) + + Context("RemoveDir volume mount directory", func() { + It("when removed succeed", func() { + err := handler.RemoveDir(logger, container) + Expect(err).NotTo(HaveOccurred()) + + volumeMountedFiles := filepath.Join(tmpdir, fakeContainerUUID) + Eventually(volumeMountedFiles).ShouldNot(BeADirectory()) + }) + + It("when removed fail", func() { + fakeFSOperations.RemoveAllReturns(errors.New("remove error")) + + err := fakeHandler.RemoveDir(logger, container) + Expect(err).To(HaveOccurred()) + + Expect(err.Error()).To(ContainSubstring("failed-to-remove-volume-mounted-files-directory")) + }) + }) +}) diff --git a/initializer/initializer.go b/initializer/initializer.go index 777f2074..6becc06c 100644 --- a/initializer/initializer.go +++ b/initializer/initializer.go @@ -150,6 +150,7 @@ type ExecutorConfig struct { UnhealthyMonitoringInterval durationjson.Duration `json:"unhealthy_monitoring_interval,omitempty"` UseSchedulableDiskSize bool `json:"use_schedulable_disk_size,omitempty"` VolmanDriverPaths string `json:"volman_driver_paths"` + VolumeMountedFiles string `json:"volume_mounted_files"` } var ( @@ -323,6 +324,12 @@ func Initialize( return nil, nil, grouper.Members{}, err } + volumeMountedFilesHandler := containerstore.NewVolumeMountedFilesHandler( + containerstore.NewFSOperations(), + config.VolumeMountedFiles, + "/etc/cf-service-bindings", + ) + logManager := containerstore.NewLogManager() containerStore := containerstore.New( @@ -345,6 +352,7 @@ func Initialize( cellID, config.EnableUnproxiedPortMappings, config.AdvertisePreferenceForInstanceAddress, + volumeMountedFilesHandler, json.Marshal, ) diff --git a/resource_converters.go b/resource_converters.go index c25a9d00..ff14fa3c 100644 --- a/resource_converters.go +++ b/resource_converters.go @@ -19,3 +19,13 @@ func EnvironmentVariablesFromModel(envVars []*models.EnvironmentVariable) []Envi } return out } + +func VolumeMountedFilesFromModel(envFiles []*models.File) []VolumeMountedFiles { + out := make([]VolumeMountedFiles, len(envFiles)) + for i, envFile := range envFiles { + out[i].Path = envFile.Path + out[i].Content = envFile.Content + } + + return out +} diff --git a/resources.go b/resources.go index c09c3e84..9f4e8ab3 100644 --- a/resources.go +++ b/resources.go @@ -199,6 +199,7 @@ type RunInfo struct { EnableContainerProxy bool `json:"enable_container_proxy"` Sidecars []Sidecar `json:"sidecars"` LogRateLimitBytesPerSecond int64 `json:"log_rate_limit_bytes_per_second"` + VolumeMountedFiles []VolumeMountedFiles `json:"volume_mounted_files,omitempty"` } type BindMountMode uint8 @@ -227,6 +228,11 @@ type EnvironmentVariable struct { Value string `json:"value"` } +type VolumeMountedFiles struct { + Path string `json:"path"` + Content string `json:"content"` +} + type ContainerMetrics struct { MemoryUsageInBytes uint64 `json:"memory_usage_in_bytes"` DiskUsageInBytes uint64 `json:"disk_usage_in_bytes"`