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  }