Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ISSUE 50] Adding in an example for multipart upload #168

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 138 additions & 9 deletions examples/walkthrough/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,41 @@ import (

const defaultExpiration = 7 * 24 * time.Hour

// UploadAndDownloadData uploads the specified data to the specified key in the
// specified bucket, using the specified Satellite, API key, and passphrase.
func UploadAndDownloadData(ctx context.Context,
accessGrant, bucketName, uploadKey string, dataToUpload []byte) error {

func CreateProjectAndConfirmBucket(ctx context.Context, accessGrant, bucketName string) (*uplink.Project, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func CreateProjectAndConfirmBucket(ctx context.Context, accessGrant, bucketName string) (*uplink.Project, error) {
// OpenProjectAndEnsureBucket shows how to open a project and check that the bucket exists and create if it doesn't.
func OpenProjectAndEnsureBucket(ctx context.Context, accessGrant, bucketName string) (*uplink.Project, error) {

Note, it's "OpenProject", because it actually doesn't create a new project when it's missing -- just opens a new connection to the project.

Similar reason for the "EnsureBucket" -- "Confirm" would somewhat imply that it would fail if the bucket doesn't exist.

// Parse access grant, which contains necessary credentials and permissions.
access, err := uplink.ParseAccess(accessGrant)
if err != nil {
return fmt.Errorf("could not request access grant: %v", err)
return nil, fmt.Errorf("could not request access grant: %v", err)
}

// Open up the Project we will be working with.
project, err := uplink.OpenProject(ctx, access)
if err != nil {
return fmt.Errorf("could not open project: %v", err)
return nil, fmt.Errorf("could not open project: %v", err)
}
defer project.Close()

// Ensure the desired Bucket within the Project is created.
_, err = project.EnsureBucket(ctx, bucketName)
if err != nil {
return fmt.Errorf("could not ensure bucket: %v", err)
_ = project.Close()
return nil, fmt.Errorf("could not ensure bucket: %v", err)
}

return project, nil
}

// UploadAndDownloadData uploads the specified data to the specified key in the
// specified bucket, using the specified Satellite, API key, and passphrase.
func UploadAndDownloadData(ctx context.Context,
accessGrant, bucketName, uploadKey string, dataToUpload []byte) error {

// Open up the Project we will be working with.
project, err := CreateProjectAndConfirmBucket(ctx, accessGrant, bucketName)
if err != nil {
return fmt.Errorf("could not create project: %v", err)
}
defer project.Close()

// Intitiate the upload of our Object to the specified bucket and key.
upload, err := project.UploadObject(ctx, bucketName, uploadKey, &uplink.UploadOptions{
// It's possible to set an expiration date for data.
Expand Down Expand Up @@ -129,6 +140,114 @@ func CreatePublicSharedLink(ctx context.Context, accessGrant, bucketName, object
return url, nil
}

func MultipartUpload(ctx context.Context, accessGrant, bucketName, objectKey string, contents1, contents2 []byte) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verb first, so UploadMultipart

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func MultipartUpload(ctx context.Context, accessGrant, bucketName, objectKey string, contents1, contents2 []byte) error {
// MultipartUpload demonstrates how to upload object in multiple parts.
func MultipartUpload(ctx context.Context, accessGrant, bucketName, objectKey string, first, second []byte) error {

The change from contents1 -> to first, because when reading code the suffix numbers can get confusing. Especially when they are interleaved in places. It's not a big problem in this case, but we like to keep this consistent. (Also the relevant error messages need updating as well.)

Using firstPart or firstContent would be also fine.

// Create the project and ensure the bucket exists
project, err := CreateProjectAndConfirmBucket(ctx, accessGrant, bucketName)
if err != nil {
return fmt.Errorf("could not create project: %v", err)
}
defer project.Close()

// Create multipart upload
uploadInfo, err := project.BeginUpload(ctx, bucketName, objectKey, &uplink.UploadOptions{
// It's possible to set an expiration date for data.
Expires: time.Now().Add(defaultExpiration),
})

if err != nil {
return fmt.Errorf("unable to begin multipart upload: %v", err)
}

// Start the upload of part 1
part1, err := project.UploadPart(ctx, bucketName, objectKey, uploadInfo.UploadID, 1)
if err != nil {
return fmt.Errorf("unable to upload part 1: %v", err)
}

// Write the contents to part 1 upload
_, err = part1.Write(contents1)
if err != nil {
return fmt.Errorf("unable to write to part 1: %v", err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("unable to write to part 1: %v", err)
_ = part1.Close()
return fmt.Errorf("unable to write to part 1: %v", err)

}

// Commit part 1 upload
err = part1.Commit()
if err != nil {
return fmt.Errorf("unable to commit to part 1: %v", err)
}

// Start the upload of part 2
part2, err := project.UploadPart(ctx, bucketName, objectKey, uploadInfo.UploadID, 2)
if err != nil {
return fmt.Errorf("unable to upload part 2: %v", err)
}

// Write the contents to part 2 upload
_, err = part2.Write(contents2)
if err != nil {
return fmt.Errorf("unable to write to part 2: %v", err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("unable to write to part 2: %v", err)
_ = part2.Close()
return fmt.Errorf("unable to write to part 2: %v", err)

}

// Commit part 2 upload
err = part2.Commit()
if err != nil {
return fmt.Errorf("unable to commit to part 2: %v", err)
}

// Complete the multipart upload
multipartObject, err := project.CommitUpload(ctx, bucketName, objectKey, uploadInfo.UploadID, nil)
if err != nil {
return fmt.Errorf("unable to complete the upload: %v", err)
}

// Initiate a download of the same object again
download, err := project.DownloadObject(ctx, bucketName, objectKey, nil)
if err != nil {
return fmt.Errorf("could not open multipart object: %v", err)
}
defer download.Close()

// Read everything from the download stream
receivedContents, err := io.ReadAll(download)
if err != nil {
return fmt.Errorf("could not read multipart data: %v", err)
}

// Check that the downloaded data is the same as the uploaded data.
uploadedData := append(contents1, contents2...)
if !bytes.Equal(receivedContents, uploadedData) {
return fmt.Errorf("got different multipart object back: %q != %q", uploadedData, receivedContents)
}

fmt.Fprintln(os.Stderr, "success!")
fmt.Fprintln(os.Stderr, "completed multipart upload:", multipartObject.Key)

return nil
}

func createMultipartData() (contents1, contents2 []byte) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Pascal case, CreateMultipartData

Copy link
Member

@egonelbre egonelbre Apr 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this function you can use:

firstPart := bytes.Repeat([]byte("abcdefghijklm"), 1e6)
secondPart := bytes.Repeat([]byte("0123456789"), 1e6)

const minimumUploadSize = 5 * 1024 * 1024 // 5 MiB

// The first contents is the alphabet on repeat
alphabet := "abcdefghijklmnopqrstuvwxyz"

contents1 = make([]byte, minimumUploadSize)
alphabetLength := len(alphabet)
for i := 0; i < minimumUploadSize; i++ {
contents1[i] = alphabet[i%alphabetLength]
}

// The second contents is numbers on repeat
numbers := "1234567890"
numbersLength := len(numbers)
contents2 = make([]byte, minimumUploadSize)
for i := 0; i < minimumUploadSize; i++ {
contents2[i] = numbers[i%numbersLength]
}

return contents1, contents2
}

func main() {
ctx := context.Background()
accessGrant := flag.String("access", os.Getenv("ACCESS_GRANT"), "access grant from satellite")
Expand All @@ -151,4 +270,14 @@ func main() {

fmt.Println("success!")
fmt.Println("public link:", url)

largeObjectKey := "foo/bar/large"

largeContents1, largeContents2 := createMultipartData()

err = MultipartUpload(ctx, *accessGrant, bucketName, largeObjectKey, largeContents1, largeContents2)
if err != nil {
fmt.Fprintln(os.Stderr, "creating mulipart upload failed:", err)
os.Exit(1)
}
Comment on lines +273 to +282
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
largeObjectKey := "foo/bar/large"
largeContents1, largeContents2 := createMultipartData()
err = MultipartUpload(ctx, *accessGrant, bucketName, largeObjectKey, largeContents1, largeContents2)
if err != nil {
fmt.Fprintln(os.Stderr, "creating mulipart upload failed:", err)
os.Exit(1)
}
largeObjectKey := "foo/bar/large"
largeFirstPart := bytes.Repeat([]byte("abcdefghijklm"), 1e6)
largeSecondPart := bytes.Repeat([]byte("0123456789"), 1e6)
err = MultipartUpload(ctx, *accessGrant, bucketName, largeObjectKey, largeFirstPart, largeSecondPart)
if err != nil {
fmt.Fprintln(os.Stderr, "creating mulipart upload failed:", err)
os.Exit(1)
}

}