Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update ZipHandler and Methods #229

Merged
merged 13 commits into from
Nov 13, 2024
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ COPY --from=builder /home/myLowPrivilegeUser/app/reportstream-sftp-ingestion /us
RUN adduser -S myLowPrivilegeUser
USER myLowPrivilegeUser

WORKDIR /home/myLowPrivilegeUser/

ENTRYPOINT ["/usr/local/bin/reportstream-sftp-ingestion"]

EXPOSE 8080
1 change: 1 addition & 0 deletions azure_functions/local.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"AzureWebJobs.HttpExample.Disabled": "true",
"AZURE_STORAGE_CONNECTION_STRING": "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:12000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:12001/devstoreaccount1;",
"POLLING_TRIGGER_QUEUE_NAME": "polling-trigger-queue",
"WEBSITE_TIME_ZONE": "America/Los_Angeles",
pluckyswan marked this conversation as resolved.
Show resolved Hide resolved
"CA_DPH_POLLING_CRON": "0 */1 * * * *"
},
"Host": {
Expand Down
8 changes: 4 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ services:
environment:
AZURE_STORAGE_CONNECTION_STRING: DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://sftp-Azurite:10000/devstoreaccount1;QueueEndpoint=http://sftp-Azurite:10001/devstoreaccount1; # pragma: allowlist secret
ENV: local
# Uncomment the line below to call local report stream. Otherwise we'll use a mock response
# Uncomment the line below to call local report stream. Otherwise, we'll use a mock response
# REPORT_STREAM_URL_PREFIX: http://host.docker.internal:7071
CA_PHL_CLIENT_NAME: flexion.simulated-lab
QUEUE_MAX_DELIVERY_ATTEMPTS: 5
POLLING_TRIGGER_QUEUE_NAME: polling-trigger-queue
volumes:
# map to Azurite data objects to the build directory
- ./localdata/data/reportstream:/localdata
- ./mock_credentials:/mock_credentials
- ./localdata/data/reportstream:/home/myLowPrivilegeUser/localdata
- ./mock_credentials:/home/myLowPrivilegeUser/mock_credentials
ports:
- "8081:8080" # default api endpoint port
platform: linux/amd64
Expand Down Expand Up @@ -73,7 +73,7 @@ services:
- -c
- |
cd /home/site/wwwroot
npm install -g azure-functions-core-tools
npm install -g azure-functions-core-tools@4.0.6280
pluckyswan marked this conversation as resolved.
Show resolved Hide resolved
pluckyswan marked this conversation as resolved.
Show resolved Hide resolved
pluckyswan marked this conversation as resolved.
Show resolved Hide resolved
npm install
npm run start
environment:
Expand Down
7 changes: 4 additions & 3 deletions src/sftp/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,21 +242,22 @@ func (receiver *SftpHandler) copySingleFile(fileInfo os.FileInfo, index int, dir
isZip := strings.Contains(fileInfo.Name(), ".zip")
if isZip {
// write file to local filesystem
err = os.WriteFile(fileInfo.Name(), fileBytes, 0644) // permissions = owner read/write, group read, other read
zipFileName := fileInfo.Name()
err = os.WriteFile(zipFileName, fileBytes, 0644) // permissions = owner read/write, group read, other read
if err != nil {
slog.Error("Failed to write file", slog.Any(utils.ErrorKey, err), slog.String("name", fileInfo.Name()))
return
}

err = receiver.zipHandler.Unzip(fileInfo.Name())
err = receiver.zipHandler.Unzip(zipFileName, blobPath)
if err != nil {
slog.Error("Failed to unzip file", slog.Any(utils.ErrorKey, err))
} else {
deleteZip = true
}

//delete file from local filesystem
err = os.Remove(fileInfo.Name())
err = os.Remove(zipFileName)
pluckyswan marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
slog.Error("Failed to remove file from local server", slog.Any(utils.ErrorKey, err), slog.String("name", fileInfo.Name()))
}
Expand Down
4 changes: 2 additions & 2 deletions src/sftp/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,8 +532,8 @@ type MockZipHandler struct {
mock.Mock
}

func (receiver *MockZipHandler) Unzip(zipFilePath string) error {
args := receiver.Called(zipFilePath)
func (receiver *MockZipHandler) Unzip(zipFilePath string, blobPath string) error {
args := receiver.Called(zipFilePath, blobPath)
return args.Error(0)
}

Expand Down
55 changes: 45 additions & 10 deletions src/zip/zip.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io"
"log/slog"
"path/filepath"
"strings"
)

type ZipHandler struct {
Expand All @@ -18,7 +19,7 @@ type ZipHandler struct {
}

type ZipHandlerInterface interface {
Unzip(zipFilePath string) error
Unzip(zipFilePath string, blobPath string) error
ExtractAndUploadSingleFile(f *zip.File, zipPassword string, zipFilePath string, errorList []FileError) []FileError
UploadErrorList(zipFilePath string, errorList []FileError, err error) error
}
Expand Down Expand Up @@ -52,20 +53,24 @@ func NewZipHandler() (ZipHandler, error) {
// to begin processing. It collects any errors with individual subfiles and uploads that information as well. An error
// is only returned from the function when we cannot handle the main zip file for some reason or have failed to upload
// the error list about the contents
func (zipHandler ZipHandler) Unzip(zipFilePath string) error {

slog.Info("Preparing to unzip", slog.String("zipFilePath", zipFilePath))
func (zipHandler ZipHandler) Unzip(zipFileName string, blobPath string) error {
slog.Info("Preparing to unzip", slog.String("zipFileName", zipFileName))
zipPasswordSecret := utils.CA_PHL + "-zip-password-" + utils.EnvironmentName() // pragma: allowlist secret
zipPassword, err := zipHandler.credentialGetter.GetSecret(zipPasswordSecret)

if err != nil {
slog.Error("Unable to get zip password", slog.Any(utils.ErrorKey, err), slog.String("KeyName", zipPasswordSecret))

// move zip file from unzip -> unzip/failure
zipHandler.MoveZip(blobPath, utils.FailureFolder)
return err
}

zipReader, err := zipHandler.zipClient.OpenReader(zipFilePath)
zipReader, err := zipHandler.zipClient.OpenReader(zipFileName)

if err != nil {
slog.Error("Failed to open zip reader", slog.Any(utils.ErrorKey, err))
zipHandler.MoveZip(blobPath, utils.FailureFolder)
return err
}
defer zipReader.Close()
Expand All @@ -74,17 +79,43 @@ func (zipHandler ZipHandler) Unzip(zipFilePath string) error {

// loop over contents
for _, f := range zipReader.File {
errorList = zipHandler.ExtractAndUploadSingleFile(f, zipPassword, zipFilePath, errorList)
errorList = zipHandler.ExtractAndUploadSingleFile(f, zipPassword, zipFileName, errorList)
}

// if errorList has contents -> move zip file from unzip -> unzip/failure
if len(errorList) > 0 {
slog.Info("Error list length over zero")
zipHandler.MoveZip(blobPath, utils.FailureFolder)
} else {
// else -> move zip file from unzip -> unzip/success
slog.Info("Error list length is zero")
zipHandler.MoveZip(blobPath, utils.SuccessFolder)
}


// Upload error info if any
err = zipHandler.UploadErrorList(zipFilePath, errorList, err)
err = zipHandler.UploadErrorList(blobPath, errorList, err)
if err != nil {
return err
}

return nil
}

// MoveZip moves a file from 'unzip' into the specified subfolder e.g. 'success', 'failure'
func (zipHandler ZipHandler) MoveZip(blobPath string, subfolder string) {
slog.Info("About to move file", slog.String("blobPath", blobPath), slog.String("destination subfolder", subfolder))
// url must include the container name e.g. 'sftp/unzip/cheeseburger.zip' vs 'unzip/cheeseburger.zip'
pluckyswan marked this conversation as resolved.
Show resolved Hide resolved
sourceUrl := filepath.Join(utils.ContainerName, blobPath)
destinationUrl := strings.Replace(sourceUrl, utils.UnzipFolder, filepath.Join(utils.UnzipFolder, subfolder), 1)
err := zipHandler.blobHandler.MoveFile(sourceUrl, destinationUrl)
if err != nil {
slog.Error("Unable to move file to "+destinationUrl, slog.Any(utils.ErrorKey, err))
} else {
slog.Info("Successfully moved file to "+destinationUrl)
}
}

func (zipHandler ZipHandler) ExtractAndUploadSingleFile(f *zip.File, zipPassword string, zipFilePath string, errorList []FileError) []FileError {
slog.Info("Extracting file", slog.String(utils.FileNameKey, f.Name), slog.String("zipFilePath", zipFilePath))

Expand Down Expand Up @@ -116,21 +147,25 @@ func (zipHandler ZipHandler) ExtractAndUploadSingleFile(f *zip.File, zipPassword
errorList = append(errorList, FileError{Filename: f.Name, ErrorMessage: err.Error()})
return errorList
}

slog.Info("uploaded file to blob for import", slog.String(utils.FileNameKey, f.Name), slog.String("zipFilePath", zipFilePath))
return errorList
}

// uploadErrorList takes a list of file-specific errors and uploads them to a single file named after the containing zip
// UploadErrorList takes a list of file-specific errors and uploads them to a single file named after the containing zip
func (zipHandler ZipHandler) UploadErrorList(zipFilePath string, errorList []FileError, err error) error {
if len(errorList) > 0 {
fileContents := ""
for _, fileError := range errorList {

fileContents += fileError.Filename + ": " + fileError.ErrorMessage + "\n"
}

err = zipHandler.blobHandler.UploadFile([]byte(fileContents), filepath.Join(utils.FailureFolder, zipFilePath+".txt"))
errorDestinationPath := strings.Replace(zipFilePath, utils.UnzipFolder, filepath.Join(utils.UnzipFolder, utils.FailureFolder), 1) + ".txt"
err = zipHandler.blobHandler.UploadFile([]byte(fileContents), errorDestinationPath)

if err != nil {
slog.Error("Failed to upload failure file", slog.Any(utils.ErrorKey, err), slog.String("zipFilePath", zipFilePath))
slog.Error("Failed to upload failure file", slog.Any(utils.ErrorKey, err), slog.String("errorDestinationPath", errorDestinationPath))
return err
}
}
Expand Down
Loading
Loading