0
votes

I have this Azure function, which has the code to upload a video to my YouTube channel whenever there is a video file in the "video" container. The code I am using is from the YouTube API sample code: https://developers.google.com/youtube/v3/docs/videos/insert . I have taken this code, and I put it inside an Azure function(code below). When I compile and run the function locally, it runs fine and I am able to upload a video using Azure Storage Explorer(below is the console output log) and I see the videos get uploaded in my YouTube channel fine.

However, when I publish the function to Azure portal, and run the function, the function just TimeOuts because it is expecting a UI as it requires user interaction for sign-in. I am wondering how can I get over this OAuth issue? Is there a way to add workflow step in Azure app that prompts the user for credentials? I would like to be able to use my function on Azure without it timing out.

Error when I publish function to Azure: enter image description here

Console Output when I run locally:

Now listening on: http://0.0.0.0:7071
Application started. Press Ctrl+C to shut down.
[3/26/2020 4:46:17 AM] Host lock lease acquired by instance ID '000000000000000000000000A74A8599'.
[3/26/2020 4:48:27 AM] Executing 'Function1' (Reason='New blob detected: video/Test.mp4', Id=62898b7f-878f-48b2-ace1-dee34a884652)
[3/26/2020 4:48:46 AM] test.mp4
[3/26/2020 4:48:46 AM] Getting client secrets.
[3/26/2020 4:48:46 AM] Done getting secrets.
[3/26/2020 4:48:46 AM] Creating youtube service
[3/26/2020 4:48:46 AM] Done creating service.
[3/26/2020 4:48:46 AM] Trying to upload video
[3/26/2020 4:49:21 AM] Done uploading video.
[3/26/2020 4:49:21 AM] Executed 'Function1' (Succeeded, Id=62898b7f-878f-48b2-ace1-dee34a884652)

Client_Secrets.json:

{

  "installed": {
    "client_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com",
    "project_id": "testindexer",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_secret": "xxxxxxxxxxxxxxxxxxxxxxxx",
    "redirect_uris": [ "urn:ietf:wg:oauth:2.0:oob", "http://localhost" ]
  }
}


using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;


namespace AzureFunctionToUploadToYoutube
{
    public static class Function1
    {
        [FunctionName("Function1")]

        public static async Task Run([BlobTrigger("video/{name}", Connection = "AzureWebJobsStorage")]Stream myBlob, string name, Microsoft.Azure.WebJobs.ExecutionContext context, ILogger log)
        {
            log.LogInformation("Function triggered by blob." + name);
            UserCredential credential;
            log.LogInformation("Getting client secrets.");

            using (var stream = new FileStream(System.IO.Path.Combine(context.FunctionAppDirectory, "client_secrets.json"), FileMode.Open, FileAccess.Read))
            {
                credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                GoogleClientSecrets.Load(stream).Secrets,
                new[] { YouTubeService.Scope.YoutubeUpload },
                "user",
                CancellationToken.None
                new FileDataStore("Google.Apis.Auth.OAuth2.Responses.TokenResponse-use",true)
                );
            }
            log.LogInformation("Done getting secrets.");
            log.LogInformation("Creating youtube service");

            var youtubeService = new YouTubeService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
            });

            log.LogInformation("Done creating service.");
            var video = new Video();
            video.Snippet = new VideoSnippet();
            video.Snippet.Title = name;
            video.Snippet.Description = "Seattle Channel";
            video.Snippet.Tags = new string[] { "tag1", "tag2" };
            video.Snippet.CategoryId = "22";
            video.Status = new VideoStatus();
            video.Status.PrivacyStatus = "unlisted";
            var VideoInsertRequest = youtubeService.Videos.Insert(video, "snippet,status", myBlob, "video/*");

            log.LogInformation("Trying to upload video");

            try
            {
                await VideoInsertRequest.UploadAsync();
                log.LogInformation("Done uploading video.");
            }

            catch (Exception ex)
            {
                log.LogInformation("Error uploading video: " + ex.Message);
            }
        }
    }
}
1

1 Answers

1
votes

GoogleWebAuthorizationBroker.AuthorizeAsync

Is for installed applications. It opens the authorization web browser window on the machine that its running on your Azure function cant spawn a web browser window and even if it could you couldn't login to the server where its running and authenticate it.

solution

authenticate the code once on your machine. Go to the %appdata% folder on windows and find the credentials file. Set the FileDataStore to load from a location that your function can understand. Filedatastore demystifiled and upload the file with your code.

filedatastore

Filedatastore is used to store the credentials return from the user default is not to include it and the file will be stored in %appdata%. You can supply a location. or no location and it "should" go in the current directory.

What matters is that the library if it detects a file will load the credentials in that file rather then prompting for login which is exactly what you want to happen in this case.

using (var stream = new FileStream(System.IO.Path.Combine(context.FunctionAppDirectory, "client_secrets.json"), FileMode.Open, FileAccess.Read))
            {
                credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                GoogleClientSecrets.Load(stream).Secrets,
                new[] { YouTubeService.Scope.YoutubeUpload },
                "user",
                CancellationToken.None,
                new FileDataStore("credfolder",true)                              

                );
            } 

IDatastore.

If you still cant get it to work the only other option I can think of would be to make your own implementation of Idatastore which would allow you to supply the values from the credential file directly in your code rather than saving it to a file. Note you only need to send the refresh token. I dont have an example of that exactly but i do have a few examples of how to create your own implementation of idatastore it may help you work it out Datastores gists