github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/azureBlobUpload.go (about) 1 package cmd 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "os" 9 "path/filepath" 10 11 "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" 12 "github.com/SAP/jenkins-library/pkg/log" 13 "github.com/SAP/jenkins-library/pkg/telemetry" 14 "github.com/go-playground/validator/v10" 15 ) 16 17 // AzureContainerAPI is used to mock Azure containerClients in unit tests 18 type azureContainerAPI interface { 19 NewBlockBlobClient(blobName string) (*azblob.BlockBlobClient, error) 20 } 21 22 // newBlockBlobClient creates a blockBlobClient from a containerClient 23 func newBlockBlobClient(blobName string, api azureContainerAPI) (*azblob.BlockBlobClient, error) { 24 return api.NewBlockBlobClient(blobName) 25 } 26 27 // uploadFileFunc uploads a file to an Azure Blob Storage 28 // The function uses the UploadFile function from the Azure SDK 29 // We introduce this 'wrapper' for mocking reasons 30 func uploadFileFunc(ctx context.Context, blobClient *azblob.BlockBlobClient, file *os.File, o azblob.UploadOption) (*http.Response, error) { 31 return blobClient.UploadFile(ctx, file, o) 32 } 33 34 // Struct to store Azure credentials from specified JSON string 35 type azureCredentials struct { 36 SASToken string `json:"sas_token" validate:"required"` 37 AccountName string `json:"account_name" validate:"required"` 38 Container string `json:"container_name" validate:"required"` 39 } 40 41 func azureBlobUpload(config azureBlobUploadOptions, telemetryData *telemetry.CustomData) { 42 err := runAzureBlobUpload(&config) 43 if err != nil { 44 log.Entry().WithError(err).Fatal("step execution failed") 45 } 46 } 47 48 func runAzureBlobUpload(config *azureBlobUploadOptions) error { 49 containerClient, err := setup(config) 50 if err != nil { 51 return err 52 } 53 return executeUpload(config, containerClient, uploadFileFunc) 54 } 55 56 func setup(config *azureBlobUploadOptions) (*azblob.ContainerClient, error) { 57 // Read credentials from JSON String 58 log.Entry().Infoln("Start reading Azure Credentials") 59 var creds azureCredentials 60 61 err := json.Unmarshal([]byte(config.JSONCredentialsAzure), &creds) 62 if err != nil { 63 log.SetErrorCategory(log.ErrorConfiguration) 64 return nil, fmt.Errorf("Could not read JSONCredentialsAzure: %w", err) 65 } 66 67 // Validate credentials (check for nil values in struct) 68 if err = validate(&creds); err != nil { 69 return nil, fmt.Errorf("Azure credentials are not valid: %w", err) 70 } 71 72 // Initialize Azure Service Client 73 sasURL := fmt.Sprintf("https://%s.blob.core.windows.net/?%s", creds.AccountName, creds.SASToken) 74 serviceClient, err := azblob.NewServiceClientWithNoCredential(sasURL, nil) 75 if err != nil { 76 log.SetErrorCategory(log.ErrorService) 77 return nil, fmt.Errorf("Could not instantiate Azure Service Client: %w", err) 78 } 79 80 // Get a containerClient from ServiceClient 81 containerClient, err := serviceClient.NewContainerClient(creds.Container) 82 if err != nil { 83 log.SetErrorCategory(log.ErrorService) 84 return nil, fmt.Errorf("Could not instantiate Azure Container Client from Azure Service Client: %w", err) 85 } 86 return containerClient, nil 87 } 88 89 // Validate validates the Azure credentials (checks for empty fields in struct) 90 func validate(creds *azureCredentials) error { 91 validate := validator.New() 92 if err := validate.Struct(creds); err != nil { 93 return err 94 } 95 return nil 96 } 97 98 func executeUpload(config *azureBlobUploadOptions, containerClient azureContainerAPI, uploadFunc func(ctx context.Context, api *azblob.BlockBlobClient, file *os.File, o azblob.UploadOption) (*http.Response, error)) error { 99 log.Entry().Infof("Starting walk through FilePath '%v'", config.FilePath) 100 101 // All Blob Operations operate with context.Context, in our case the clients do not expire 102 ctx := context.Background() 103 104 // Iterate through directories 105 err := filepath.Walk(config.FilePath, func(currentFilePath string, f os.FileInfo, err error) error { 106 // Handle Failure to prevent panic (e.g. in case of an invalid filepath) 107 if err != nil { 108 log.SetErrorCategory(log.ErrorConfiguration) 109 return fmt.Errorf("Failed to access path: %w", err) 110 } 111 // Skip directories, only upload files 112 if !f.IsDir() { 113 log.Entry().Infof("Current target path is: '%v'", currentFilePath) 114 115 //Read Data from File 116 data, e := os.Open(currentFilePath) 117 if e != nil { 118 log.SetErrorCategory(log.ErrorInfrastructure) 119 return fmt.Errorf("Could not read the file '%s': %w", currentFilePath, e) 120 } 121 defer data.Close() 122 123 // Create a filepath in UNIX format so that the BlockBlobClient automatically detects directories 124 key := filepath.ToSlash(currentFilePath) 125 126 // Get a blockBlobClient from containerClient 127 blockBlobClient, e := newBlockBlobClient(key, containerClient) 128 if e != nil { 129 log.SetErrorCategory(log.ErrorService) 130 return fmt.Errorf("Could not instantiate Azure blockBlobClient from Azure Container Client: %w", e) 131 } 132 133 // Upload File 134 log.Entry().Infof("Start upload of file '%v'", currentFilePath) 135 _, e = uploadFunc(ctx, blockBlobClient, data, azblob.UploadOption{}) 136 if e != nil { 137 log.SetErrorCategory(log.ErrorService) 138 return fmt.Errorf("There was an error during the upload of file '%v': %w", currentFilePath, e) 139 } 140 141 log.Entry().Infof("Upload of file '%v' was successful!", currentFilePath) 142 return e 143 } 144 return nil 145 }) 146 147 if err == nil { 148 log.Entry().Infoln("Upload has successfully finished!") 149 } 150 return err 151 }