Skip to content

Commit 456867d

Browse files
authored
Added manifest file (#1033)
* feat: create manifest file * chore: updated manifest file * chore: fixing manifest for delete * chore: clean up * chore: update the manifest structure * chore: created internal manifest model * chore: lint * chore: fix err * chore: lints * chore: rename * chore: unit tests * chore: more unit tests * chore: fix unit test * chore: lint * chore: rollback in case of manifest file update fails * chore: review updates * review updates * chore: updated the script to create the dir during install * chore: rename
1 parent d3b6b69 commit 456867d

File tree

6 files changed

+185
-14
lines changed

6 files changed

+185
-14
lines changed

.nfpm.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ contents:
3434
packager: rpm
3535
- dst: /var/log/nginx-agent
3636
type: dir
37+
- dst: /var/lib/nginx-agent
38+
type: dir
3739
overrides:
3840
deb:
3941
depends:

internal/file/file_manager_service.go

Lines changed: 136 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package file
77

88
import (
99
"context"
10+
"encoding/json"
1011
"errors"
1112
"fmt"
1213
"log/slog"
@@ -37,7 +38,16 @@ import (
3738
//go:generate go run github.com/maxbrunsfeld/counterfeiter/[email protected] -generate
3839
//counterfeiter:generate . fileManagerServiceInterface
3940

40-
const maxAttempts = 5
41+
const (
42+
maxAttempts = 5
43+
dirPerm = 0o755
44+
filePerm = 0o600
45+
)
46+
47+
var (
48+
manifestDirPath = "/var/lib/nginx-agent"
49+
manifestFilePath = manifestDirPath + "/manifest.json"
50+
)
4151

4252
type (
4353
fileOperator interface {
@@ -313,9 +323,13 @@ func (fms *FileManagerService) ConfigApply(ctx context.Context,
313323
if fileErr != nil {
314324
return model.RollbackRequired, fileErr
315325
}
316-
326+
fileOverviewFiles := files.ConvertToMapOfFiles(fileOverview.GetFiles())
317327
// Update map of current files on disk
318-
fms.UpdateCurrentFilesOnDisk(files.ConvertToMapOfFiles(fileOverview.GetFiles()))
328+
fms.UpdateCurrentFilesOnDisk(fileOverviewFiles)
329+
manifestFileErr := fms.UpdateManifestFile(fileOverviewFiles)
330+
if manifestFileErr != nil {
331+
return model.RollbackRequired, manifestFileErr
332+
}
319333

320334
return model.OK, nil
321335
}
@@ -325,8 +339,10 @@ func (fms *FileManagerService) ClearCache() {
325339
clear(fms.fileActions)
326340
}
327341

342+
// nolint:revive,cyclop
328343
func (fms *FileManagerService) Rollback(ctx context.Context, instanceID string) error {
329344
slog.InfoContext(ctx, "Rolling back config for instance", "instanceid", instanceID)
345+
areFilesUpdated := false
330346
fms.filesMutex.Lock()
331347
defer fms.filesMutex.Unlock()
332348
for _, file := range fms.fileActions {
@@ -338,11 +354,11 @@ func (fms *FileManagerService) Rollback(ctx context.Context, instanceID string)
338354

339355
// currentFilesOnDisk needs to be updated after rollback action is performed
340356
delete(fms.currentFilesOnDisk, file.GetFileMeta().GetName())
357+
areFilesUpdated = true
341358

342359
continue
343360
case mpi.File_FILE_ACTION_DELETE, mpi.File_FILE_ACTION_UPDATE:
344361
content := fms.rollbackFileContents[file.GetFileMeta().GetName()]
345-
346362
err := fms.fileOperator.Write(ctx, content, file.GetFileMeta())
347363
if err != nil {
348364
return err
@@ -351,13 +367,21 @@ func (fms *FileManagerService) Rollback(ctx context.Context, instanceID string)
351367
// currentFilesOnDisk needs to be updated after rollback action is performed
352368
file.GetFileMeta().Hash = files.GenerateHash(content)
353369
fms.currentFilesOnDisk[file.GetFileMeta().GetName()] = file
370+
areFilesUpdated = true
354371
case mpi.File_FILE_ACTION_UNSPECIFIED, mpi.File_FILE_ACTION_UNCHANGED:
355372
fallthrough
356373
default:
357374
slog.DebugContext(ctx, "File Action not implemented")
358375
}
359376
}
360377

378+
if areFilesUpdated {
379+
manifestFileErr := fms.UpdateManifestFile(fms.currentFilesOnDisk)
380+
if manifestFileErr != nil {
381+
return manifestFileErr
382+
}
383+
}
384+
361385
return nil
362386
}
363387

@@ -446,7 +470,7 @@ func (fms *FileManagerService) checkAllowedDirectory(checkFiles []*mpi.File) err
446470

447471
// DetermineFileActions compares two sets of files to determine the file action for each file. Returns a map of files
448472
// that have changed and a map of the contents for each updated and deleted file. Key to both maps is file path
449-
// nolint: revive
473+
// nolint: revive,cyclop
450474
func (fms *FileManagerService) DetermineFileActions(currentFiles, modifiedFiles map[string]*mpi.File) (
451475
map[string]*mpi.File, map[string][]byte, error,
452476
) {
@@ -461,26 +485,31 @@ func (fms *FileManagerService) DetermineFileActions(currentFiles, modifiedFiles
461485
fileDiff := make(map[string]*mpi.File) // Files that have changed, key is file name
462486
fileContents := make(map[string][]byte) // contents of the file, key is file name
463487

464-
// if file is in currentFiles but not in modified files, file has been deleted
488+
manifestFiles, manifestFileErr := fms.manifestFile(currentFiles)
489+
490+
if manifestFileErr != nil && manifestFiles == nil {
491+
return nil, nil, manifestFileErr
492+
}
493+
// if file is in manifestFiles but not in modified files, file has been deleted
465494
// copy contents, set file action
466-
for _, currentFile := range currentFiles {
467-
fileName := currentFile.GetFileMeta().GetName()
468-
_, ok := modifiedFiles[fileName]
495+
for fileName, currentFile := range manifestFiles {
496+
_, exists := modifiedFiles[fileName]
469497

470-
if !ok {
498+
if !exists {
499+
// Read file contents before marking it deleted
471500
fileContent, readErr := os.ReadFile(fileName)
472501
if readErr != nil {
473502
return nil, nil, fmt.Errorf("error reading file %s, error: %w", fileName, readErr)
474503
}
475504
fileContents[fileName] = fileContent
476505
currentFile.Action = &deleteAction
477-
fileDiff[currentFile.GetFileMeta().GetName()] = currentFile
506+
fileDiff[fileName] = currentFile
478507
}
479508
}
480509

481510
for _, file := range modifiedFiles {
482511
fileName := file.GetFileMeta().GetName()
483-
currentFile, ok := currentFiles[file.GetFileMeta().GetName()]
512+
currentFile, ok := manifestFiles[file.GetFileMeta().GetName()]
484513
// default to unchanged action
485514
file.Action = &unchangedAction
486515

@@ -521,3 +550,98 @@ func (fms *FileManagerService) UpdateCurrentFilesOnDisk(currentFiles map[string]
521550
fms.currentFilesOnDisk[file.GetFileMeta().GetName()] = file
522551
}
523552
}
553+
554+
func (fms *FileManagerService) UpdateManifestFile(currentFiles map[string]*mpi.File) (err error) {
555+
manifestFiles := fms.convertToManifestFileMap(currentFiles)
556+
manifestJSON, err := json.MarshalIndent(manifestFiles, "", " ")
557+
if err != nil {
558+
return fmt.Errorf("unable to marshal manifest file json: %w", err)
559+
}
560+
561+
// 0755 allows read/execute for all, write for owner
562+
if err = os.MkdirAll(manifestDirPath, dirPerm); err != nil {
563+
return fmt.Errorf("unable to create directory %s: %w", manifestDirPath, err)
564+
}
565+
566+
// 0600 ensures only root can read/write
567+
newFile, err := os.OpenFile(manifestFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, filePerm)
568+
if err != nil {
569+
return fmt.Errorf("failed to read manifest file: %w", err)
570+
}
571+
defer newFile.Close()
572+
573+
_, err = newFile.Write(manifestJSON)
574+
if err != nil {
575+
return fmt.Errorf("failed to write manifest file: %w", err)
576+
}
577+
578+
return nil
579+
}
580+
581+
func (fms *FileManagerService) manifestFile(currentFiles map[string]*mpi.File) (map[string]*mpi.File, error) {
582+
if _, err := os.Stat(manifestFilePath); err != nil {
583+
return currentFiles, err // Return current files if manifest directory still doesn't exist
584+
}
585+
586+
file, err := os.ReadFile(manifestFilePath)
587+
if err != nil {
588+
return nil, fmt.Errorf("failed to read manifest file: %w", err)
589+
}
590+
591+
var manifestFiles map[string]*model.ManifestFile
592+
593+
err = json.Unmarshal(file, &manifestFiles)
594+
if err != nil {
595+
return nil, fmt.Errorf("failed to parse manifest file: %w", err)
596+
}
597+
598+
fileMap := fms.convertToFileMap(manifestFiles)
599+
600+
return fileMap, nil
601+
}
602+
603+
func (fms *FileManagerService) convertToManifestFileMap(
604+
currentFiles map[string]*mpi.File,
605+
) map[string]*model.ManifestFile {
606+
manifestFileMap := make(map[string]*model.ManifestFile)
607+
608+
for name, file := range currentFiles {
609+
if file == nil || file.GetFileMeta() == nil {
610+
continue
611+
}
612+
manifestFile := fms.convertToManifestFile(file)
613+
manifestFileMap[name] = manifestFile
614+
}
615+
616+
return manifestFileMap
617+
}
618+
619+
func (fms *FileManagerService) convertToManifestFile(file *mpi.File) *model.ManifestFile {
620+
return &model.ManifestFile{
621+
ManifestFileMeta: &model.ManifestFileMeta{
622+
Name: file.GetFileMeta().GetName(),
623+
Size: file.GetFileMeta().GetSize(),
624+
Hash: file.GetFileMeta().GetHash(),
625+
},
626+
}
627+
}
628+
629+
func (fms *FileManagerService) convertToFileMap(manifestFiles map[string]*model.ManifestFile) map[string]*mpi.File {
630+
currentFileMap := make(map[string]*mpi.File)
631+
for name, manifestFile := range manifestFiles {
632+
currentFile := fms.convertToFile(manifestFile)
633+
currentFileMap[name] = currentFile
634+
}
635+
636+
return currentFileMap
637+
}
638+
639+
func (fms *FileManagerService) convertToFile(manifestFile *model.ManifestFile) *mpi.File {
640+
return &mpi.File{
641+
FileMeta: &mpi.FileMeta{
642+
Name: manifestFile.ManifestFileMeta.Name,
643+
Hash: manifestFile.ManifestFileMeta.Hash,
644+
Size: manifestFile.ManifestFileMeta.Size,
645+
},
646+
}
647+
}

internal/file/file_manager_service_test.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ func TestFileManagerService_ConfigApply_Add(t *testing.T) {
153153

154154
overview := protos.FileOverview(filePath, fileHash)
155155

156+
manifestDirPath = tempDir
157+
manifestFilePath = manifestDirPath + "/manifest.json"
158+
helpers.CreateFileWithErrorCheck(t, manifestDirPath, "manifest.json")
159+
156160
fakeFileServiceClient := &v1fakes.FakeFileServiceClient{}
157161
fakeFileServiceClient.GetOverviewReturns(&mpi.GetOverviewResponse{
158162
Overview: overview,
@@ -201,6 +205,10 @@ func TestFileManagerService_ConfigApply_Update(t *testing.T) {
201205
},
202206
}
203207

208+
manifestDirPath = tempDir
209+
manifestFilePath = manifestDirPath + "/manifest.json"
210+
helpers.CreateFileWithErrorCheck(t, manifestDirPath, "manifest.json")
211+
204212
overview := protos.FileOverview(tempFile.Name(), fileHash)
205213

206214
fakeFileServiceClient := &v1fakes.FakeFileServiceClient{}
@@ -253,6 +261,10 @@ func TestFileManagerService_ConfigApply_Delete(t *testing.T) {
253261
},
254262
}
255263

264+
manifestDirPath = tempDir
265+
manifestFilePath = manifestDirPath + "/manifest.json"
266+
helpers.CreateFileWithErrorCheck(t, manifestDirPath, "manifest.json")
267+
256268
fakeFileServiceClient := &v1fakes.FakeFileServiceClient{}
257269
agentConfig := types.AgentConfig()
258270
agentConfig.AllowedDirectories = []string{tempDir}
@@ -368,6 +380,10 @@ func TestFileManagerService_Rollback(t *testing.T) {
368380
_, writeErr = updateFile.Write(newFileContent)
369381
require.NoError(t, writeErr)
370382

383+
manifestDirPath = tempDir
384+
manifestFilePath = manifestDirPath + "/manifest.json"
385+
helpers.CreateFileWithErrorCheck(t, manifestDirPath, "manifest.json")
386+
371387
filesCache := map[string]*mpi.File{
372388
addFile.Name(): {
373389
FileMeta: &mpi.FileMeta{
@@ -468,6 +484,10 @@ func TestFileManagerService_DetermineFileActions(t *testing.T) {
468484
unmanagedErr := os.WriteFile(unmanagedFile.Name(), unmanagedFileContent, 0o600)
469485
require.NoError(t, unmanagedErr)
470486

487+
manifestDirPath = tempDir
488+
manifestFilePath = manifestDirPath + "/manifest.json"
489+
helpers.CreateFileWithErrorCheck(t, manifestDirPath, "manifest.json")
490+
471491
tests := []struct {
472492
expectedError error
473493
modifiedFiles map[string]*mpi.File
@@ -504,7 +524,7 @@ func TestFileManagerService_DetermineFileActions(t *testing.T) {
504524
},
505525
expectedCache: map[string]*mpi.File{
506526
deleteTestFile.Name(): {
507-
FileMeta: protos.FileMeta(deleteTestFile.Name(), files.GenerateHash(fileContent)),
527+
FileMeta: protos.ManifestFileMeta(deleteTestFile.Name(), files.GenerateHash(fileContent)),
508528
Action: &deleteAction,
509529
},
510530
updateTestFile.Name(): {
@@ -556,9 +576,10 @@ func TestFileManagerService_DetermineFileActions(t *testing.T) {
556576
t.Run(test.name, func(tt *testing.T) {
557577
fakeFileServiceClient := &v1fakes.FakeFileServiceClient{}
558578
fileManagerService := NewFileManagerService(fakeFileServiceClient, types.AgentConfig())
579+
err = fileManagerService.UpdateManifestFile(test.currentFiles)
580+
require.NoError(tt, err)
559581
diff, contents, fileActionErr := fileManagerService.DetermineFileActions(test.currentFiles,
560582
test.modifiedFiles)
561-
562583
require.NoError(tt, fileActionErr)
563584
assert.Equal(tt, test.expectedContent, contents)
564585
assert.Equal(tt, test.expectedCache, diff)

internal/model/config.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ type APIDetails struct {
2727
Location string
2828
}
2929

30+
type ManifestFile struct {
31+
ManifestFileMeta *ManifestFileMeta `json:"manifest_file_meta"`
32+
}
33+
34+
type ManifestFileMeta struct {
35+
// The full path of the file
36+
Name string `json:"name"`
37+
// The hash of the file contents sha256, hex encoded
38+
Hash string `json:"hash"`
39+
// The size of the file in bytes
40+
Size int64 `json:"size"`
41+
}
42+
3043
// Complexity is 11, allowed is 10
3144
// nolint: revive, cyclop
3245
func (ncc *NginxConfigContext) Equal(otherNginxConfigContext *NginxConfigContext) bool {

scripts/packages/.local-nfpm.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ contents:
3434
packager: rpm
3535
- dst: /var/log/nginx-agent
3636
type: dir
37+
- dst: /var/lib/nginx-agent
38+
type: dir
3739
overrides:
3840
deb:
3941
depends:

test/protos/files.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ func FileMeta(fileName, fileHash string) *mpi.FileMeta {
2121
}
2222
}
2323

24+
func ManifestFileMeta(fileName, fileHash string) *mpi.FileMeta {
25+
return &mpi.FileMeta{
26+
ModifiedTime: nil,
27+
Name: fileName,
28+
Hash: fileHash,
29+
Permissions: "",
30+
}
31+
}
32+
2433
func CertMeta(fileName, fileHash string) *mpi.FileMeta {
2534
lastModified, _ := CreateProtoTime("2024-01-09T13:22:21Z")
2635

0 commit comments

Comments
 (0)