// UploadImage takes the metadata and credentials required to upload the image specified by `fileName`
// It can speed up the upload by using goroutines. The number of parallel goroutines is bounded by
// the `threads` argument.
UploadImage takes the metadata and credentials required to upload the image specified by `fileName`
It can speed up the upload by using goroutines. The number of parallel goroutines is bounded by
the `threads` argument.
// UploadImage takes the metadata and credentials required to upload the image specified by ` fileName ` // It can speed up the upload by using goroutines . The number of parallel goroutines is bounded by // the ` threads ` argument .
func UploadImage(credentials Credentials, metadata ImageMetadata, fileName string, threads int) error {
	if !strings.HasSuffix(metadata.ImageName, ".vhd") {
		metadata.ImageName = metadata.ImageName + ".vhd"
	}
	credential, err := azblob.NewSharedKeyCredential(credentials.StorageAccount, credentials.StorageAccessKey)
	if err != nil {
		return fmt.Errorf("cannot create azure credentials: %v", err)
	}
	p := azblob.NewPipeline(credential, azblob.PipelineOptions{})
	URL, _ := url.Parse(fmt.Sprintf("https://%s.blob.core.windows.net/%s", credentials.StorageAccount, metadata.ContainerName))
	containerURL := azblob.NewContainerURL(*URL, p)
	ctx := context.Background()
	imageFile, err := os.Open(fileName)
	if err != nil {
		return fmt.Errorf("cannot open the image: %v", err)
	}
	defer imageFile.Close()
	stat, err := imageFile.Stat()
	if err != nil {
		return fmt.Errorf("cannot stat the image: %v", err)
	}
	imageFileHash := md5.New()
	if _, err := io.Copy(imageFileHash, imageFile); err != nil {
		return fmt.Errorf("cannot create md5 of the image: %v", err)
	}
	if _, err := imageFile.Seek(0, 0); err != nil {
		return fmt.Errorf("cannot seek the image: %v", err)
	}
	blobURL := newPageBlobURL(containerURL, metadata.ImageName)
	_, err = blobURL.Create(ctx, stat.Size(), 0, azblob.BlobHTTPHeaders{}, azblob.Metadata{}, azblob.BlobAccessConditions{})
	if err != nil {
		return fmt.Errorf("cannot create the blob URL: %v", err)
	}
	_, err = blobURL.SetHTTPHeaders(ctx, azblob.BlobHTTPHeaders{ContentMD5: imageFileHash.Sum(nil)}, azblob.BlobAccessConditions{})
	if err != nil {
		return fmt.Errorf("cannot set the HTTP headers on the blob URL: %v", err)
	}
	var semaphore = make(chan int, threads)
	var errorInGoroutine = make(chan error, 1)
	var counter int64 = 0
	reader := bufio.NewReader(imageFile)
	run := true
	var wg sync.WaitGroup
	for run {
		buffer := make([]byte, azblob.PageBlobMaxUploadPagesBytes)
		n, err := reader.Read(buffer)
		if err != nil {
			if err == io.EOF {
				run = false
			} else {
				return fmt.Errorf("reading the image failed: %v", err)
			}
		}
		if n == 0 {
			break
		}
		wg.Add(1)
		semaphore <- 1
		go func(counter int64, buffer []byte, n int) {
			defer wg.Done()
			_, err = blobURL.UploadPages(ctx, counter*azblob.PageBlobMaxUploadPagesBytes, bytes.NewReader(buffer[:n]), azblob.PageBlobAccessConditions{}, nil)
			if err != nil {
				err = fmt.Errorf("uploading a page failed: %v", err)
				select {
				case errorInGoroutine <- err:
				default:
				}
			}
			<-semaphore
		}(counter, buffer, n)
		counter++
	}
	wg.Wait()
	select {
	case err := <-errorInGoroutine:
		return err
	default:
	}
	props, err := blobURL.GetProperties(ctx, azblob.BlobAccessConditions{})
	if err != nil {
		return fmt.Errorf("getting the properties of the new blob failed: %v", err)
	}
	var blobChecksum []byte = props.ContentMD5()
	var fileChecksum []byte = imageFileHash.Sum(nil)
	if !bytes.Equal(blobChecksum, fileChecksum) {
		return errors.New("error during image upload. the image seems to be corrupted")
	}
	return nil
}
func UploadImage ( credentials Credentials , metadata ImageMetadata , fileName string , threads int ) error { if ! strings . HasSuffix ( metadata . ImageName , " " ) { metadata . ImageName = metadata . ImageName + " " } credential , err := azblob . NewSharedKeyCredential ( credentials . StorageAccount , credentials . StorageAccessKey ) if err != nil { return fmt . Errorf ( " " , err ) } p := azblob . NewPipeline ( credential , azblob . PipelineOptions { } ) URL , _ := url . Parse ( fmt . Sprintf ( " " , credentials . StorageAccount , metadata . ContainerName ) ) containerURL := azblob . NewContainerURL ( * URL , p ) ctx := context . Background ( ) imageFile , err := os . Open ( fileName ) if err != nil { return fmt . Errorf ( " " , err ) } defer imageFile . Close ( ) stat , err := imageFile . Stat ( ) if err != nil { return fmt . Errorf ( " " , err ) } imageFileHash := md5 . New ( ) if _ , err := io . Copy ( imageFileHash , imageFile ) ; err != nil { return fmt . Errorf ( " " , err ) } if _ , err := imageFile . Seek ( 0 , 0 ) ; err != nil { return fmt . Errorf ( " " , err ) } blobURL := newPageBlobURL ( containerURL , metadata . ImageName ) _ , err = blobURL . Create ( ctx , stat . Size ( ) , 0 , azblob . BlobHTTPHeaders { } , azblob . Metadata { } , azblob . BlobAccessConditions { } ) if err != nil { return fmt . Errorf ( " " , err ) } _ , err = blobURL . SetHTTPHeaders ( ctx , azblob . BlobHTTPHeaders { ContentMD5 : imageFileHash . Sum ( nil ) } , azblob . BlobAccessConditions { } ) if err != nil { return fmt . Errorf ( " " , err ) } var semaphore = make ( chan int , threads ) var errorInGoroutine = make ( chan error , 1 ) var counter int64 = 0 reader := bufio . NewReader ( imageFile ) run := true var wg sync . WaitGroup for run { buffer := make ( [ ] byte , azblob . PageBlobMaxUploadPagesBytes ) n , err := reader . Read ( buffer ) if err != nil { if err == io . EOF { run = false } else { return fmt . Errorf ( " " , err ) } } if n == 0 { break } wg . Add ( 1 ) semaphore <- 1 go func ( counter int64 , buffer [ ] byte , n int ) { defer wg . Done ( ) _ , err = blobURL . UploadPages ( ctx , counter * azblob . PageBlobMaxUploadPagesBytes , bytes . NewReader ( buffer [ : n ] ) , azblob . PageBlobAccessConditions { } , nil ) if err != nil { err = fmt . Errorf ( " " , err ) select { case errorInGoroutine <- err : default : } } <- semaphore } ( counter , buffer , n ) counter ++ } wg . Wait ( ) select { case err := <- errorInGoroutine : return err default : } props , err := blobURL . GetProperties ( ctx , azblob . BlobAccessConditions { } ) if err != nil { return fmt . Errorf ( " " , err ) } var blobChecksum [ ] byte = props . ContentMD5 ( ) var fileChecksum [ ] byte = imageFileHash . Sum ( nil ) if ! bytes . Equal ( blobChecksum , fileChecksum ) { return errors . New ( " " ) } return nil }
UploadImage takes the metadata and credentials required to upload the image specified by `fileName`
It can speed up the upload by using goroutines.
UploadImage takes the metadata and credentials required to upload the image specified by ` fileName ` It can speed up the upload by using goroutines .
// Azure cannot create an image from a storage blob without .vhd extension // Create a default request pipeline using your storage account name and account key. // get storage account blob service URL endpoint. // Create a ContainerURL object that wraps the container URL and a request // pipeline to make requests. // Create the container, use a never-expiring context // Open the image file for reading // Stat image to get the file size // Hash the imageFile // Move the cursor back to the start of the imageFile // Create page blob URL. Page blob is required for VM images // Wrong MD5 does not seem to have any impact on the upload // Create control variables // This channel simulates behavior of a semaphore and bounds the number of parallel threads // Forward error from goroutine to the caller // Create buffered reader to speed up the upload // Run the upload // Send the error to the error channel in a non-blocking way. If there is already an error, just discard this one // Wait for all goroutines to finish // Check any errors during the transmission using a nonblocking read from the channel // Check properties, specifically MD5 sum of the blob