1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-14 11:03:09 +02:00
sap-jenkins-library/cmd/azureBlobUpload.go

152 lines
5.2 KiB
Go
Raw Normal View History

Create azureBlobUpload (#3766) * add Step azureBlobUpload * add azure sdk and unit tests * add Documentation * fix Groovy Wrapper * adopt the requested changes from awsS3Upload * fix lint tests * downgrade azure sdk to go 1.17 * multiple fixes e.g. use of temporary files for tests * fix tests * Update cmd/azureBlobUpload.go Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com> * Update cmd/azureBlobUpload.go Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com> * Update documentation/docs/steps/azureBlobUpload.md Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com> * Update documentation/docs/steps/azureBlobUpload.md Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com> * Update documentation/docs/steps/azureBlobUpload.md Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com> * Update documentation/docs/steps/azureBlobUpload.md Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com> * requested changes * use latest version of azure sdk after update to go 1.18 * change staticcheck from 1.1.0 to 1.2.0 * try to fix lint test by pre-compiling go 1.18 * fix caching for lint test * improve error handling by dividing runner * improve error handling and add validation * multiple naming fixes * add new test for unmarshalling JSON-Structs * Update cmd/azureBlobUpload_test.go Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com> * Update cmd/azureBlobUpload_test.go Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com> * Update cmd/azureBlobUpload_test.go Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com> * fix JSON unmarshall test * Update documentation/docs/steps/azureBlobUpload.md Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com> * Update cmd/azureBlobUpload_test.go Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com> * Update cmd/azureBlobUpload.go Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com> * fix uploadFunc Co-authored-by: Thorsten Duda <thorsten.duda@sap.com> Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com>
2022-06-15 09:41:02 +02:00
package cmd
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/go-playground/validator/v10"
)
// AzureContainerAPI is used to mock Azure containerClients in unit tests
type azureContainerAPI interface {
NewBlockBlobClient(blobName string) (*azblob.BlockBlobClient, error)
}
// newBlockBlobClient creates a blockBlobClient from a containerClient
func newBlockBlobClient(blobName string, api azureContainerAPI) (*azblob.BlockBlobClient, error) {
return api.NewBlockBlobClient(blobName)
}
// uploadFileFunc uploads a file to an Azure Blob Storage
// The function uses the UploadFile function from the Azure SDK
// We introduce this 'wrapper' for mocking reasons
func uploadFileFunc(ctx context.Context, blobClient *azblob.BlockBlobClient, file *os.File, o azblob.UploadOption) (*http.Response, error) {
return blobClient.UploadFile(ctx, file, o)
}
// Struct to store Azure credentials from specified JSON string
type azureCredentials struct {
SASToken string `json:"sas_token" validate:"required"`
AccountName string `json:"account_name" validate:"required"`
Container string `json:"container_name" validate:"required"`
}
func azureBlobUpload(config azureBlobUploadOptions, telemetryData *telemetry.CustomData) {
err := runAzureBlobUpload(&config)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func runAzureBlobUpload(config *azureBlobUploadOptions) error {
containerClient, err := setup(config)
if err != nil {
return err
}
return executeUpload(config, containerClient, uploadFileFunc)
}
func setup(config *azureBlobUploadOptions) (*azblob.ContainerClient, error) {
// Read credentials from JSON String
log.Entry().Infoln("Start reading Azure Credentials")
var creds azureCredentials
err := json.Unmarshal([]byte(config.JSONCredentialsAzure), &creds)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return nil, fmt.Errorf("Could not read JSONCredentialsAzure: %w", err)
}
// Validate credentials (check for nil values in struct)
if err = validate(&creds); err != nil {
return nil, fmt.Errorf("Azure credentials are not valid: %w", err)
}
// Initialize Azure Service Client
sasURL := fmt.Sprintf("https://%s.blob.core.windows.net/?%s", creds.AccountName, creds.SASToken)
serviceClient, err := azblob.NewServiceClientWithNoCredential(sasURL, nil)
if err != nil {
log.SetErrorCategory(log.ErrorService)
return nil, fmt.Errorf("Could not instantiate Azure Service Client: %w", err)
}
// Get a containerClient from ServiceClient
containerClient, err := serviceClient.NewContainerClient(creds.Container)
if err != nil {
log.SetErrorCategory(log.ErrorService)
return nil, fmt.Errorf("Could not instantiate Azure Container Client from Azure Service Client: %w", err)
}
return containerClient, nil
}
// Validate validates the Azure credentials (checks for empty fields in struct)
func validate(creds *azureCredentials) error {
validate := validator.New()
if err := validate.Struct(creds); err != nil {
return err
}
return nil
}
func executeUpload(config *azureBlobUploadOptions, containerClient azureContainerAPI, uploadFunc func(ctx context.Context, api *azblob.BlockBlobClient, file *os.File, o azblob.UploadOption) (*http.Response, error)) error {
log.Entry().Infof("Starting walk through FilePath '%v'", config.FilePath)
// All Blob Operations operate with context.Context, in our case the clients do not expire
ctx := context.Background()
// Iterate through directories
err := filepath.Walk(config.FilePath, func(currentFilePath string, f os.FileInfo, err error) error {
// Handle Failure to prevent panic (e.g. in case of an invalid filepath)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return fmt.Errorf("Failed to access path: %w", err)
}
// Skip directories, only upload files
if !f.IsDir() {
log.Entry().Infof("Current target path is: '%v'", currentFilePath)
//Read Data from File
data, e := os.Open(currentFilePath)
if e != nil {
log.SetErrorCategory(log.ErrorInfrastructure)
return fmt.Errorf("Could not read the file '%s': %w", currentFilePath, e)
}
defer data.Close()
// Create a filepath in UNIX format so that the BlockBlobClient automatically detects directories
key := filepath.ToSlash(currentFilePath)
// Get a blockBlobClient from containerClient
blockBlobClient, e := newBlockBlobClient(key, containerClient)
if e != nil {
log.SetErrorCategory(log.ErrorService)
return fmt.Errorf("Could not instantiate Azure blockBlobClient from Azure Container Client: %w", e)
}
// Upload File
log.Entry().Infof("Start upload of file '%v'", currentFilePath)
_, e = uploadFunc(ctx, blockBlobClient, data, azblob.UploadOption{})
if e != nil {
log.SetErrorCategory(log.ErrorService)
return fmt.Errorf("There was an error during the upload of file '%v': %w", currentFilePath, e)
}
log.Entry().Infof("Upload of file '%v' was successful!", currentFilePath)
return e
}
return nil
})
if err == nil {
log.Entry().Infoln("Upload has successfully finished!")
}
return err
}