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

Unable to use this sdk to upload a file using an upload session to OneDrive #511

Closed
aakashbanerjee opened this issue Jun 12, 2023 · 5 comments · Fixed by microsoftgraph/msgraph-sdk-go-core#231
Assignees
Labels
question Further information is requested

Comments

@aakashbanerjee
Copy link

aakashbanerjee commented Jun 12, 2023

Primary Intent:
Upload a file to OneDrive

What I have tried so far:
Application registration created in Azure AD with required permissions and secret.

Used the App Only Client Credentials flow to create NewClientSecretCredential and graph service client.

	graphcred, err := azidentity.NewClientSecretCredential(
		tenantID,
		clientID,
		clientSecret,
		nil,
	)
	if err != nil {
		log.Fatal(err)
	}

grclient, err := msgraphsdk.NewGraphServiceClientWithCredentials(graphcred, []string{"https://graph.microsoft.com/.default"})
	if err != nil {
		log.Fatal(err)
	}

I have used these client to Get details of the Drive as below where I want to upload the file.

result, err := grclient.Drives().ByDriveId(driveID).Get(context.Background(), nil)
	if err != nil {
		fmt.Printf("Error getting the drive: %v\n", err)
		printOdataError(err)
	}
	fmt.Printf("Found Drive : %v\n", *result.GetId())

Following docs here : https://learn.microsoft.com/en-us/graph/api/driveitem-createuploadsession?view=graph-rest-1.0#create-an-upload-session I am trying to create an upload session

//Create an upload session

uploadSessionRequestBuilder := grclient.Drives().ByDriveId(driveID).Items().ByDriveItemId(filePath).CreateUploadSession()

I am able to get a response and print it.

log.Println(*uploadSessionRequestBuilder) //value below

`{{map[baseurl:https://graph.microsoft.com/v1.0 drive%2Did:xxxxxxx driveItem%2Did:file.txt] 0x1400012caf0 {+baseurl}/drives/{drive%2Did}/items/{driveItem%2Did}/createUploadSession}}`

Where is this struct defined/documented so I can understand what the fields are?

log.Printf("%T", uploadSessionRequestBuilder) //type: *drives.ItemItemsItemCreateUploadSessionRequestBuilder

I am unable to understand or move forward with how to use this uploadSessionRequestBuilder to actually upload the file to OneDrive.

Any help is much appreciated. Please advise if I should rather some other recommended sdk to programmatically upload files to OneDrive.

@ghost ghost added the Needs Triage 🔍 label Jun 12, 2023
@baywet baywet added question Further information is requested Needs Attention 👋 and removed Needs Triage 🔍 labels Jun 12, 2023
@aakashbanerjee
Copy link
Author

I did a little more digging. As I understand the next step is to call the Post() method defined in https://github.com/microsoftgraph/msgraph-sdk-go/blob/v1.6.0/drives/item_items_item_create_upload_session_request_builder.go

So using the uploadSessionRequestBuilder created in the previous step I tried this below. The second argument has to be of type ItemItemsItemCreateUploadSessionPostRequestBodyable (these names are so long and confusing). ItemItemsItemCreateUploadSessionPostRequestBodyable is an interface type and if I pass a nil as below.
uploadSessionable, err := uploadSessionRequestBuilder.Post(
context.Background(),
nil,
nil,
)

I get an error error: error status code received from the APIcode: itemNotFoundmsg: Item not found

// ItemItemsItemCreateUploadSessionPostRequestBodyable
type ItemItemsItemCreateUploadSessionPostRequestBodyable interface {
i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder
ie8677ce2c7e1b4c22e9c3827ecd078d41185424dd9eeb92b7d971ed2d49a392e.BackedModel
i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable
GetBackingStore()(ie8677ce2c7e1b4c22e9c3827ecd078d41185424dd9eeb92b7d971ed2d49a392e.BackingStore)
GetItem()(iadcd81124412c61e647227ecfc4449d8bba17de0380ddda76f641a29edf2b242.DriveItemUploadablePropertiesable)
SetBackingStore(value ie8677ce2c7e1b4c22e9c3827ecd078d41185424dd9eeb92b7d971ed2d49a392e.BackingStore)()
SetItem(value iadcd81124412c61e647227ecfc4449d8bba17de0380ddda76f641a29edf2b242.DriveItemUploadablePropertiesable)()
}

ItemItemsItemCreateUploadSessionPostRequestBody implements this interface ItemItemsItemCreateUploadSessionPostRequestBodyable so a value of type can be passed as the second argument to the uploadSessionRequestBuilder.Post method above. What should I pass if I am trying to just upload a file "file.txt" sitting in the local project root.

Tried this below.

requestBody := &drives.ItemItemsItemCreateUploadSessionPostRequestBody{}
body := &models.DriveItemUploadableProperties{}
body.SetName(&filePath)
requestBody.SetItem(body)

and instead of nil for the second arg passing requestBody which panics.
are there some values that need to be set that I am not setting in the Post call? Not sure if I barking up the wrong tree, first time working with this sdk.

@rkodev
Copy link
Contributor

rkodev commented Jul 12, 2023

Hi @aakashbanerjee, thanks for trying the Graph SDK. You are currently attemping to create an upload session which would ideally be ready for use when the feature Large file upload is ready. This feature is currently in progress and should be release in the next month.

I would not recommend this approach if you are a beginner but If you would like to use the upload session to upload a file before the feature is complete, here is some sample code that you can get started on to use to create an upload session

import (
	"context"
	"fmt"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	abstractions "github.com/microsoft/kiota-abstractions-go"
	msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go"
	"github.com/microsoftgraph/msgraph-sdk-go/drives"
	"github.com/microsoftgraph/msgraph-sdk-go/models"
	"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
	"log"
	"os"
)

func SampleFileUpload() {

	tenantID := "tenantID"
	clientID := "clientID"
	clientSecret := "clientSecret"

	graphcred, err := azidentity.NewClientSecretCredential(
		tenantID,
		clientID,
		clientSecret,
		nil,
	)
	if err != nil {
		log.Fatal(err)
	}

	grclient, err := msgraphsdk.NewGraphServiceClientWithCredentials(graphcred, []string{"https://graph.microsoft.com/.default"})
	if err != nil {
		log.Fatal(err)
	}

	// create upload session
	driveID := "driveID"
	result, err := grclient.Drives().ByDriveId(driveID).Get(context.Background(), nil)
	if err != nil {
		fmt.Printf("Error getting the drive: %v\n", err)
		printOdataError(err)
	}
	fmt.Printf("Found Drive : %v\n", *result.GetId())

	// read file info

	data, err := os.ReadFile("path\\to\\file")
	if err != nil {
		panic(err)
	}

	// create upload session
	driveItemId := "driveItemId"

	requestBody := drives.NewItemItemsItemCreateUploadSessionPostRequestBody()
	desc := "description"
	name := "name"
	size := int64(len(data))

	uploadProperties := models.NewDriveItemUploadableProperties()
	uploadProperties.SetDescription(&desc)
	uploadProperties.SetName(&name)
	uploadProperties.SetFileSize(&size)
	requestBody.SetItem(uploadProperties)
	requestBody.SetAdditionalData(map[string]any{"@microsoft.graph.conflictBehavior": "replace"})

	uploadSession, err := grclient.Drives().ByDriveId(driveID).Items().ByDriveItemId(driveItemId).CreateUploadSession().Post(context.Background(), requestBody, nil)
	if err != nil {
		log.Fatal(err)
	}

	// create an upload request information
	requestInfo := abstractions.NewRequestInformation()
	requestInfo.UrlTemplate = *uploadSession.GetUploadUrl()
	requestInfo.Method = abstractions.PUT
	requestInfo.Headers.Add("Content-Type", "application/octet-stream")
	requestInfo.Headers.Add("Content-Length", fmt.Sprintf("%d", len(data)))
	requestInfo.SetStreamContent(data)

	// upload file
	errorMapping := abstractions.ErrorMappings{
		"4XX": odataerrors.CreateODataErrorFromDiscriminatorValue,
		"5XX": odataerrors.CreateODataErrorFromDiscriminatorValue,
	}
	err = grclient.BaseRequestBuilder.RequestAdapter.SendNoContent(context.Background(), requestInfo, errorMapping)
	if err != nil {
		log.Fatal(err)
	}
}

func printOdataError(err error) {
 // excluded for brevity
}

@microsoft-github-policy-service
Copy link
Contributor

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment.

@aakashbanerjee
Copy link
Author

aakashbanerjee commented Jul 17, 2023

Thanks for that. I ended up using microsoft-authentication-library-for-go/apps/confidential and net/http

fuperr := putFileInOneDrive(f, driveID, authority, clientID, clientSecret)
if fuperr!=nil{
  log.Fatal(fuperr)
}

func putFileInOneDrive(f *os.File, driveID, authority, clientID, clientSecret string) error{
cred, err := confidential.NewCredFromSecret(clientSecret)
if err!=nil{
return err
}

confidentialClientApp, err := confidential.New(authority, clientID, cred)
if err!=nil{
return err
}

result, err:= confidentialClientApp.AcquireTokenByCredential(context.Background(), []string{"https://graph.microsoft.com/.default"})
if err!=nil{
return err
}

accessToken := result.AccessToken

file,err := os.Open(f.Name())
if err!=nil{
return err
}

defer file.Close()

client := &http.Client{}
req,err := http.NewRequest("PUT", "https://graph.microsoft.com/v1.0/drives/"+driveID+"/items/root:/FolderName/"+f.Name()+":/content", file)
if err!=nil{
return err
}

req.Header.Set("Authorization", "Bearer "+accessToken)

resp,err := client.Do(req)
if err!=nil{
return err
}
defer resp.Body.Close()
//check resp.StatusCode and resp.StatusCreated and handle accordingly
return nil
}

To use OneDrive file upload it seems there is a req for a SPO license. As far as I remember I enabled a Microsoft 365 subscription.

@gerardkok
Copy link

gerardkok commented Sep 10, 2023

@rkodev I'm afraid I can't get your sample code to work. I'm rather new to MS365 in general, and the msgraph Go sdk in particular, so perhaps I'm missing something obvious. I'm using an app that has the right permissions (as far as I can see), and I'm on a business license.

The problem seems to be in creating the upload properties. If I only inlcude the 'name' property, i.e.:

        ...
	requestBody := drives.NewItemItemsItemCreateUploadSessionPostRequestBody()
	name := "filename.txt"

	uploadProperties := models.NewDriveItemUploadableProperties()
	uploadProperties.SetName(&name)
	requestBody.SetItem(uploadProperties)

	uploadSession, err := client.Drives().ByDriveIdString(<my user id>).Items().ByDriveItemIdString("root").CreateUploadSession().Post(context.Background(), requestBody, nil)
        ...

then the OData error I receive is:

error: error status code received from the API
code: invalidRequest
msg: A valid path must be provided.

If I add another field to the upload properties, i.e. either 'description' or 'size', the error changes:

error: error status code received from the API
code: invalidRequest
msg: Invalid request

I've tried prepending the filename in the upload properties with '/', '/root', '/root:', '/root:/', etc. but that doesn't fix the error. Is there perhaps a different way to construct these upload properties?

Edited to add that the method posted by @aakashbanerjee works, so permissions seem to be correct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants