mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
dd0aae6ded
* 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>
152 lines
5.2 KiB
Go
152 lines
5.2 KiB
Go
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
|
|
}
|