diff --git a/internal/command/command_plugin.go b/internal/command/command_plugin.go index e3315720b..616c48eb8 100644 --- a/internal/command/command_plugin.go +++ b/internal/command/command_plugin.go @@ -228,18 +228,36 @@ func (cp *CommandPlugin) processDataPlaneResponse(ctx context.Context, msg *bus. } func (cp *CommandPlugin) processConnectionReset(ctx context.Context, msg *bus.Message) { + var subscribeCtx context.Context slog.DebugContext(ctx, "Command plugin received connection reset message") + if newConnection, ok := msg.Data.(grpc.GrpcConnectionInterface); ok { + slog.DebugContext(ctx, "Canceling Subscribe after connection reset") + ctxWithMetadata := cp.config.NewContextWithLabels(ctx) + cp.subscribeMutex.Lock() + defer cp.subscribeMutex.Unlock() + + if cp.subscribeCancel != nil { + cp.subscribeCancel() + slog.DebugContext(ctxWithMetadata, "Successfully canceled subscribe after connection reset") + } + connectionErr := cp.conn.Close(ctx) if connectionErr != nil { slog.ErrorContext(ctx, "Command plugin: unable to close connection", "error", connectionErr) } + cp.conn = newConnection err := cp.commandService.UpdateClient(ctx, cp.conn.CommandServiceClient()) if err != nil { slog.ErrorContext(ctx, "Failed to reset connection", "error", err) return } + + slog.DebugContext(ctxWithMetadata, "Starting new subscribe after connection reset") + subscribeCtx, cp.subscribeCancel = context.WithCancel(ctxWithMetadata) + go cp.commandService.Subscribe(subscribeCtx) + slog.DebugContext(ctx, "Command service client reset successfully") } } diff --git a/internal/file/file_manager_service.go b/internal/file/file_manager_service.go index 162800f64..3c7b51251 100644 --- a/internal/file/file_manager_service.go +++ b/internal/file/file_manager_service.go @@ -52,7 +52,7 @@ type ( reader *bufio.Reader, chunkID uint32, ) (mpi.FileDataChunk_Content, error) - WriteManifestFile(updatedFiles map[string]*model.ManifestFile, + WriteManifestFile(ctx context.Context, updatedFiles map[string]*model.ManifestFile, manifestDir, manifestPath string) (writeError error) } @@ -68,6 +68,7 @@ type ( fileToUpdate *mpi.File, ) error SetIsConnected(isConnected bool) + UpdateClient(ctx context.Context, fileServiceClient mpi.FileServiceClient) } fileManagerServiceInterface interface { @@ -85,6 +86,7 @@ type ( ) (map[string]*model.FileCache, map[string][]byte, error) IsConnected() bool SetIsConnected(isConnected bool) + ResetClient(ctx context.Context, fileServiceClient mpi.FileServiceClient) } ) @@ -101,6 +103,7 @@ type FileManagerService struct { currentFilesOnDisk map[string]*mpi.File // key is file path previousManifestFiles map[string]*model.ManifestFile manifestFilePath string + rollbackManifest bool filesMutex sync.RWMutex } @@ -116,10 +119,16 @@ func NewFileManagerService(fileServiceClient mpi.FileServiceClient, agentConfig currentFilesOnDisk: make(map[string]*mpi.File), previousManifestFiles: make(map[string]*model.ManifestFile), manifestFilePath: agentConfig.ManifestDir + "/manifest.json", + rollbackManifest: true, manifestLock: manifestLock, } } +func (fms *FileManagerService) ResetClient(ctx context.Context, fileServiceClient mpi.FileServiceClient) { + fms.fileServiceOperator.UpdateClient(ctx, fileServiceClient) + slog.DebugContext(ctx, "File manager service reset client successfully") +} + func (fms *FileManagerService) IsConnected() bool { return fms.fileServiceOperator.IsConnected() } @@ -131,6 +140,7 @@ func (fms *FileManagerService) SetIsConnected(isConnected bool) { func (fms *FileManagerService) ConfigApply(ctx context.Context, configApplyRequest *mpi.ConfigApplyRequest, ) (status model.WriteStatus, err error) { + fms.rollbackManifest = true fileOverview := configApplyRequest.GetOverview() if fileOverview == nil { @@ -161,6 +171,7 @@ func (fms *FileManagerService) ConfigApply(ctx context.Context, fileErr := fms.executeFileActions(ctx) if fileErr != nil { + fms.rollbackManifest = false return model.RollbackRequired, fileErr } fileOverviewFiles := files.ConvertToMapOfFiles(fileOverview.GetFiles()) @@ -179,6 +190,7 @@ func (fms *FileManagerService) ClearCache() { clear(fms.previousManifestFiles) } +//nolint:revive // cognitive-complexity of 13 max is 12, loop is needed cant be broken up func (fms *FileManagerService) Rollback(ctx context.Context, instanceID string) error { slog.InfoContext(ctx, "Rolling back config for instance", "instance_id", instanceID) @@ -212,10 +224,13 @@ func (fms *FileManagerService) Rollback(ctx context.Context, instanceID string) } } - manifestFileErr := fms.fileOperator.WriteManifestFile(fms.previousManifestFiles, - fms.agentConfig.ManifestDir, fms.manifestFilePath) - if manifestFileErr != nil { - return manifestFileErr + if fms.rollbackManifest { + slog.DebugContext(ctx, "Rolling back manifest file", "manifest_previous", fms.previousManifestFiles) + manifestFileErr := fms.fileOperator.WriteManifestFile(ctx, fms.previousManifestFiles, + fms.agentConfig.ManifestDir, fms.manifestFilePath) + if manifestFileErr != nil { + return manifestFileErr + } } return nil @@ -374,7 +389,7 @@ func (fms *FileManagerService) UpdateCurrentFilesOnDisk( fms.currentFilesOnDisk[currentFile.GetFileMeta().GetName()] = currentFile } - err := fms.UpdateManifestFile(currentFiles, referenced) + err := fms.UpdateManifestFile(ctx, currentFiles, referenced) if err != nil { return fmt.Errorf("failed to update manifest file: %w", err) } @@ -385,12 +400,24 @@ func (fms *FileManagerService) UpdateCurrentFilesOnDisk( // seems to be a control flag, avoid control coupling // //nolint:revive // referenced is a required flag -func (fms *FileManagerService) UpdateManifestFile(currentFiles map[string]*mpi.File, referenced bool) (err error) { - slog.Debug("Updating manifest file", "current_files", currentFiles, "referenced", referenced) +func (fms *FileManagerService) UpdateManifestFile(ctx context.Context, + currentFiles map[string]*mpi.File, referenced bool, +) (err error) { + slog.DebugContext(ctx, "Updating manifest file", "current_files", currentFiles, "referenced", referenced) currentManifestFiles, _, readError := fms.manifestFile() + + // When agent is first started the manifest is updated when an NGINX instance is found, but the manifest file + // will be empty leading to previousManifestFiles being empty. This was causing issues if the first config + // apply failed leading to the manifest file being rolled back to an empty file. + // If the currentManifestFiles is empty then we can assume the Agent has just started and this is the first + // write of the Manifest file, so set previousManifestFiles to be the currentFiles. + if len(currentManifestFiles) == 0 { + currentManifestFiles = fms.convertToManifestFileMap(currentFiles, referenced) + } + fms.previousManifestFiles = currentManifestFiles if readError != nil && !errors.Is(readError, os.ErrNotExist) { - slog.Debug("Error reading manifest file", "current_manifest_files", + slog.DebugContext(ctx, "Error reading manifest file", "current_manifest_files", currentManifestFiles, "updated_files", currentFiles, "referenced", referenced) return fmt.Errorf("unable to read manifest file: %w", readError) @@ -416,7 +443,7 @@ func (fms *FileManagerService) UpdateManifestFile(currentFiles map[string]*mpi.F updatedFiles = manifestFiles } - return fms.fileOperator.WriteManifestFile(updatedFiles, fms.agentConfig.ManifestDir, fms.manifestFilePath) + return fms.fileOperator.WriteManifestFile(ctx, updatedFiles, fms.agentConfig.ManifestDir, fms.manifestFilePath) } func (fms *FileManagerService) manifestFile() (map[string]*model.ManifestFile, map[string]*mpi.File, error) { diff --git a/internal/file/file_manager_service_test.go b/internal/file/file_manager_service_test.go index 01a0f7cda..824730637 100644 --- a/internal/file/file_manager_service_test.go +++ b/internal/file/file_manager_service_test.go @@ -8,6 +8,7 @@ package file import ( "context" "encoding/json" + "errors" "fmt" "os" "path/filepath" @@ -68,6 +69,7 @@ func TestFileManagerService_ConfigApply_Add(t *testing.T) { assert.Equal(t, fileContent, data) assert.Equal(t, fileManagerService.fileActions[filePath].File, overview.GetFiles()[0]) assert.Equal(t, 1, fakeFileServiceClient.GetFileCallCount()) + assert.True(t, fileManagerService.rollbackManifest) } func TestFileManagerService_ConfigApply_Add_LargeFile(t *testing.T) { @@ -116,6 +118,7 @@ func TestFileManagerService_ConfigApply_Add_LargeFile(t *testing.T) { assert.Equal(t, fileManagerService.fileActions[filePath].File, overview.GetFiles()[0]) assert.Equal(t, 0, fakeFileServiceClient.GetFileCallCount()) assert.Equal(t, 53, int(fakeServerStreamingClient.currentChunkID)) + assert.True(t, fileManagerService.rollbackManifest) } func TestFileManagerService_ConfigApply_Update(t *testing.T) { @@ -176,6 +179,7 @@ func TestFileManagerService_ConfigApply_Update(t *testing.T) { assert.Equal(t, fileContent, data) assert.Equal(t, fileManagerService.rollbackFileContents[tempFile.Name()], previousFileContent) assert.Equal(t, fileManagerService.fileActions[tempFile.Name()].File, overview.GetFiles()[0]) + assert.True(t, fileManagerService.rollbackManifest) } func TestFileManagerService_ConfigApply_Delete(t *testing.T) { @@ -244,6 +248,42 @@ func TestFileManagerService_ConfigApply_Delete(t *testing.T) { filesOnDisk[tempFile.Name()].GetFileMeta().GetSize(), ) assert.Equal(t, model.OK, writeStatus) + assert.True(t, fileManagerService.rollbackManifest) +} + +func TestFileManagerService_ConfigApply_Failed(t *testing.T) { + ctx := t.Context() + tempDir := t.TempDir() + + filePath := filepath.Join(tempDir, "nginx.conf") + fileContent := []byte("# this is going to fail") + fileHash := files.GenerateHash(fileContent) + + overview := protos.FileOverview(filePath, fileHash) + + manifestDirPath := tempDir + manifestFilePath := manifestDirPath + "/manifest.json" + helpers.CreateFileWithErrorCheck(t, manifestDirPath, "manifest.json") + + fakeFileServiceClient := &v1fakes.FakeFileServiceClient{} + fakeFileServiceClient.GetOverviewReturns(&mpi.GetOverviewResponse{ + Overview: overview, + }, nil) + fakeFileServiceClient.GetFileReturns(nil, errors.New("file not found")) + + agentConfig := types.AgentConfig() + agentConfig.AllowedDirectories = []string{tempDir} + + fileManagerService := NewFileManagerService(fakeFileServiceClient, agentConfig, &sync.RWMutex{}) + fileManagerService.agentConfig.ManifestDir = manifestDirPath + fileManagerService.manifestFilePath = manifestFilePath + + request := protos.CreateConfigApplyRequest(overview) + writeStatus, err := fileManagerService.ConfigApply(ctx, request) + + require.Error(t, err) + assert.Equal(t, model.RollbackRequired, writeStatus) + assert.False(t, fileManagerService.rollbackManifest) } func TestFileManagerService_checkAllowedDirectory(t *testing.T) { @@ -571,7 +611,7 @@ func TestFileManagerService_DetermineFileActions(t *testing.T) { for _, test := range tests { t.Run(test.name, func(tt *testing.T) { // Delete manifest file if it already exists - manifestFile := CreateTestManifestFile(t, tempDir, test.currentFiles) + manifestFile := CreateTestManifestFile(t, tempDir, test.currentFiles, true) defer manifestFile.Close() manifestDirPath := tempDir manifestFilePath := manifestFile.Name() @@ -595,11 +635,11 @@ func TestFileManagerService_DetermineFileActions(t *testing.T) { } } -func CreateTestManifestFile(t testing.TB, tempDir string, currentFiles map[string]*mpi.File) *os.File { +func CreateTestManifestFile(t testing.TB, tempDir string, currentFiles map[string]*mpi.File, refrenced bool) *os.File { t.Helper() fakeFileServiceClient := &v1fakes.FakeFileServiceClient{} fileManagerService := NewFileManagerService(fakeFileServiceClient, types.AgentConfig(), &sync.RWMutex{}) - manifestFiles := fileManagerService.convertToManifestFileMap(currentFiles, true) + manifestFiles := fileManagerService.convertToManifestFileMap(currentFiles, refrenced) manifestJSON, err := json.MarshalIndent(manifestFiles, "", " ") require.NoError(t, err) file, err := os.CreateTemp(tempDir, "manifest.json") @@ -611,6 +651,166 @@ func CreateTestManifestFile(t testing.TB, tempDir string, currentFiles map[strin return file } +func TestFileManagerService_UpdateManifestFile(t *testing.T) { + ctx := t.Context() + fileContent := []byte("location /test {\n return 200 \"Test location\\n\";\n}") + fileHash := files.GenerateHash(fileContent) + + tests := []struct { + currentFiles map[string]*mpi.File + currentManifestFiles map[string]*model.ManifestFile + expectedFiles map[string]*model.ManifestFile + name string + referenced bool + previousReferenced bool + }{ + { + name: "Test 1: Manifest file empty", + currentFiles: map[string]*mpi.File{ + "/etc/nginx/nginx.conf": { + FileMeta: protos.FileMeta("/etc/nginx/nginx.conf", fileHash), + }, + }, + expectedFiles: map[string]*model.ManifestFile{ + "/etc/nginx/nginx.conf": { + ManifestFileMeta: &model.ManifestFileMeta{ + Name: "/etc/nginx/nginx.conf", + Hash: fileHash, + Size: 0, + Referenced: true, + }, + }, + }, + currentManifestFiles: make(map[string]*model.ManifestFile), + referenced: true, + previousReferenced: true, + }, + { + name: "Test 2: Manifest file populated - unreferenced", + currentFiles: map[string]*mpi.File{ + "/etc/nginx/nginx.conf": { + FileMeta: protos.FileMeta("/etc/nginx/nginx.conf", fileHash), + }, + "/etc/nginx/unref.conf": { + FileMeta: protos.FileMeta("/etc/nginx/unref.conf", fileHash), + }, + }, + expectedFiles: map[string]*model.ManifestFile{ + "/etc/nginx/nginx.conf": { + ManifestFileMeta: &model.ManifestFileMeta{ + Name: "/etc/nginx/nginx.conf", + Hash: fileHash, + Size: 0, + Referenced: false, + }, + }, + "/etc/nginx/unref.conf": { + ManifestFileMeta: &model.ManifestFileMeta{ + Name: "/etc/nginx/unref.conf", + Hash: fileHash, + Size: 0, + Referenced: false, + }, + }, + }, + currentManifestFiles: map[string]*model.ManifestFile{ + "/etc/nginx/nginx.conf": { + ManifestFileMeta: &model.ManifestFileMeta{ + Name: "/etc/nginx/nginx.conf", + Hash: fileHash, + Size: 0, + Referenced: true, + }, + }, + }, + referenced: false, + previousReferenced: true, + }, + { + name: "Test 3: Manifest file populated - referenced", + currentFiles: map[string]*mpi.File{ + "/etc/nginx/nginx.conf": { + FileMeta: protos.FileMeta("/etc/nginx/nginx.conf", fileHash), + }, + "/etc/nginx/test.conf": { + FileMeta: protos.FileMeta("/etc/nginx/test.conf", fileHash), + }, + }, + expectedFiles: map[string]*model.ManifestFile{ + "/etc/nginx/nginx.conf": { + ManifestFileMeta: &model.ManifestFileMeta{ + Name: "/etc/nginx/nginx.conf", + Hash: fileHash, + Size: 0, + Referenced: true, + }, + }, + "/etc/nginx/test.conf": { + ManifestFileMeta: &model.ManifestFileMeta{ + Name: "/etc/nginx/test.conf", + Hash: fileHash, + Size: 0, + Referenced: true, + }, + }, + "/etc/nginx/unref.conf": { + ManifestFileMeta: &model.ManifestFileMeta{ + Name: "/etc/nginx/unref.conf", + Hash: fileHash, + Size: 0, + Referenced: false, + }, + }, + }, + currentManifestFiles: map[string]*model.ManifestFile{ + "/etc/nginx/nginx.conf": { + ManifestFileMeta: &model.ManifestFileMeta{ + Name: "/etc/nginx/nginx.conf", + Hash: fileHash, + Size: 0, + Referenced: false, + }, + }, + "/etc/nginx/unref.conf": { + ManifestFileMeta: &model.ManifestFileMeta{ + Name: "/etc/nginx/unref.conf", + Hash: fileHash, + Size: 0, + Referenced: false, + }, + }, + }, + referenced: true, + previousReferenced: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(tt *testing.T) { + manifestDirPath := t.TempDir() + file := helpers.CreateFileWithErrorCheck(t, manifestDirPath, "manifest.json") + + fakeFileServiceClient := &v1fakes.FakeFileServiceClient{} + fileManagerService := NewFileManagerService(fakeFileServiceClient, types.AgentConfig(), &sync.RWMutex{}) + fileManagerService.agentConfig.ManifestDir = manifestDirPath + fileManagerService.manifestFilePath = file.Name() + + manifestJSON, err := json.MarshalIndent(test.currentManifestFiles, "", " ") + require.NoError(t, err) + + _, err = file.Write(manifestJSON) + require.NoError(t, err) + + updateErr := fileManagerService.UpdateManifestFile(ctx, test.currentFiles, test.referenced) + require.NoError(tt, updateErr) + + manifestFiles, _, manifestErr := fileManagerService.manifestFile() + require.NoError(tt, manifestErr) + assert.Equal(tt, test.expectedFiles, manifestFiles) + }) + } +} + func TestFileManagerService_fileActions(t *testing.T) { ctx := context.Background() tempDir := t.TempDir() diff --git a/internal/file/file_operator.go b/internal/file/file_operator.go index ee65925c8..ef512cadb 100644 --- a/internal/file/file_operator.go +++ b/internal/file/file_operator.go @@ -149,9 +149,10 @@ func (fo *FileOperator) ReadChunk( return chunk, err } -func (fo *FileOperator) WriteManifestFile(updatedFiles map[string]*model.ManifestFile, manifestDir, +func (fo *FileOperator) WriteManifestFile(ctx context.Context, updatedFiles map[string]*model.ManifestFile, manifestDir, manifestPath string, ) (writeError error) { + slog.DebugContext(ctx, "Writing manifest file", "updated_files", updatedFiles) manifestJSON, err := json.MarshalIndent(updatedFiles, "", " ") if err != nil { return fmt.Errorf("unable to marshal manifest file json: %w", err) diff --git a/internal/file/file_plugin.go b/internal/file/file_plugin.go index c093fe62e..39c0bdcd5 100644 --- a/internal/file/file_plugin.go +++ b/internal/file/file_plugin.go @@ -149,7 +149,7 @@ func (fp *FilePlugin) handleConnectionReset(ctx context.Context, msg *bus.Messag fp.conn = newConnection reconnect = fp.fileManagerService.IsConnected() - fp.fileManagerService = NewFileManagerService(fp.conn.FileServiceClient(), fp.config, fp.manifestLock) + fp.fileManagerService.ResetClient(ctx, fp.conn.FileServiceClient()) fp.fileManagerService.SetIsConnected(reconnect) slog.DebugContext(ctx, "File manager service client reset successfully") diff --git a/internal/file/file_service_operator.go b/internal/file/file_service_operator.go index 4d902bcd1..80d47ec0c 100644 --- a/internal/file/file_service_operator.go +++ b/internal/file/file_service_operator.go @@ -56,6 +56,11 @@ func NewFileServiceOperator(agentConfig *config.Config, fileServiceClient mpi.Fi } } +func (fso *FileServiceOperator) UpdateClient(ctx context.Context, fileServiceClient mpi.FileServiceClient) { + fso.fileServiceClient = fileServiceClient + slog.DebugContext(ctx, "File service operator updated client") +} + func (fso *FileServiceOperator) SetIsConnected(isConnected bool) { fso.isConnected.Store(isConnected) } diff --git a/internal/file/filefakes/fake_file_manager_service_interface.go b/internal/file/filefakes/fake_file_manager_service_interface.go index 3583dd166..9d1943659 100644 --- a/internal/file/filefakes/fake_file_manager_service_interface.go +++ b/internal/file/filefakes/fake_file_manager_service_interface.go @@ -73,6 +73,12 @@ type FakeFileManagerServiceInterface struct { isConnectedReturnsOnCall map[int]struct { result1 bool } + ResetClientStub func(context.Context, v1.FileServiceClient) + resetClientMutex sync.RWMutex + resetClientArgsForCall []struct { + arg1 context.Context + arg2 v1.FileServiceClient + } RollbackStub func(context.Context, string) error rollbackMutex sync.RWMutex rollbackArgsForCall []struct { @@ -413,6 +419,39 @@ func (fake *FakeFileManagerServiceInterface) IsConnectedReturnsOnCall(i int, res }{result1} } +func (fake *FakeFileManagerServiceInterface) ResetClient(arg1 context.Context, arg2 v1.FileServiceClient) { + fake.resetClientMutex.Lock() + fake.resetClientArgsForCall = append(fake.resetClientArgsForCall, struct { + arg1 context.Context + arg2 v1.FileServiceClient + }{arg1, arg2}) + stub := fake.ResetClientStub + fake.recordInvocation("ResetClient", []interface{}{arg1, arg2}) + fake.resetClientMutex.Unlock() + if stub != nil { + fake.ResetClientStub(arg1, arg2) + } +} + +func (fake *FakeFileManagerServiceInterface) ResetClientCallCount() int { + fake.resetClientMutex.RLock() + defer fake.resetClientMutex.RUnlock() + return len(fake.resetClientArgsForCall) +} + +func (fake *FakeFileManagerServiceInterface) ResetClientCalls(stub func(context.Context, v1.FileServiceClient)) { + fake.resetClientMutex.Lock() + defer fake.resetClientMutex.Unlock() + fake.ResetClientStub = stub +} + +func (fake *FakeFileManagerServiceInterface) ResetClientArgsForCall(i int) (context.Context, v1.FileServiceClient) { + fake.resetClientMutex.RLock() + defer fake.resetClientMutex.RUnlock() + argsForCall := fake.resetClientArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + func (fake *FakeFileManagerServiceInterface) Rollback(arg1 context.Context, arg2 string) error { fake.rollbackMutex.Lock() ret, specificReturn := fake.rollbackReturnsOnCall[len(fake.rollbackArgsForCall)] @@ -585,6 +624,8 @@ func (fake *FakeFileManagerServiceInterface) Invocations() map[string][][]interf defer fake.determineFileActionsMutex.RUnlock() fake.isConnectedMutex.RLock() defer fake.isConnectedMutex.RUnlock() + fake.resetClientMutex.RLock() + defer fake.resetClientMutex.RUnlock() fake.rollbackMutex.RLock() defer fake.rollbackMutex.RUnlock() fake.setIsConnectedMutex.RLock() diff --git a/internal/file/filefakes/fake_file_operator.go b/internal/file/filefakes/fake_file_operator.go index 77445acdc..3b2b2ee6c 100644 --- a/internal/file/filefakes/fake_file_operator.go +++ b/internal/file/filefakes/fake_file_operator.go @@ -69,12 +69,13 @@ type FakeFileOperator struct { writeChunkedFileReturnsOnCall map[int]struct { result1 error } - WriteManifestFileStub func(map[string]*model.ManifestFile, string, string) error + WriteManifestFileStub func(context.Context, map[string]*model.ManifestFile, string, string) error writeManifestFileMutex sync.RWMutex writeManifestFileArgsForCall []struct { - arg1 map[string]*model.ManifestFile - arg2 string + arg1 context.Context + arg2 map[string]*model.ManifestFile arg3 string + arg4 string } writeManifestFileReturns struct { result1 error @@ -348,20 +349,21 @@ func (fake *FakeFileOperator) WriteChunkedFileReturnsOnCall(i int, result1 error }{result1} } -func (fake *FakeFileOperator) WriteManifestFile(arg1 map[string]*model.ManifestFile, arg2 string, arg3 string) error { +func (fake *FakeFileOperator) WriteManifestFile(arg1 context.Context, arg2 map[string]*model.ManifestFile, arg3 string, arg4 string) error { fake.writeManifestFileMutex.Lock() ret, specificReturn := fake.writeManifestFileReturnsOnCall[len(fake.writeManifestFileArgsForCall)] fake.writeManifestFileArgsForCall = append(fake.writeManifestFileArgsForCall, struct { - arg1 map[string]*model.ManifestFile - arg2 string + arg1 context.Context + arg2 map[string]*model.ManifestFile arg3 string - }{arg1, arg2, arg3}) + arg4 string + }{arg1, arg2, arg3, arg4}) stub := fake.WriteManifestFileStub fakeReturns := fake.writeManifestFileReturns - fake.recordInvocation("WriteManifestFile", []interface{}{arg1, arg2, arg3}) + fake.recordInvocation("WriteManifestFile", []interface{}{arg1, arg2, arg3, arg4}) fake.writeManifestFileMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3) + return stub(arg1, arg2, arg3, arg4) } if specificReturn { return ret.result1 @@ -375,17 +377,17 @@ func (fake *FakeFileOperator) WriteManifestFileCallCount() int { return len(fake.writeManifestFileArgsForCall) } -func (fake *FakeFileOperator) WriteManifestFileCalls(stub func(map[string]*model.ManifestFile, string, string) error) { +func (fake *FakeFileOperator) WriteManifestFileCalls(stub func(context.Context, map[string]*model.ManifestFile, string, string) error) { fake.writeManifestFileMutex.Lock() defer fake.writeManifestFileMutex.Unlock() fake.WriteManifestFileStub = stub } -func (fake *FakeFileOperator) WriteManifestFileArgsForCall(i int) (map[string]*model.ManifestFile, string, string) { +func (fake *FakeFileOperator) WriteManifestFileArgsForCall(i int) (context.Context, map[string]*model.ManifestFile, string, string) { fake.writeManifestFileMutex.RLock() defer fake.writeManifestFileMutex.RUnlock() argsForCall := fake.writeManifestFileArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 } func (fake *FakeFileOperator) WriteManifestFileReturns(result1 error) {