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

app.storageBlob extraOutput w/ queue binding not working #179

Closed
LuwkasLima opened this issue Oct 27, 2023 · 6 comments · Fixed by #193
Closed

app.storageBlob extraOutput w/ queue binding not working #179

LuwkasLima opened this issue Oct 27, 2023 · 6 comments · Fixed by #193
Assignees
Labels
bug Something isn't working
Milestone

Comments

@LuwkasLima
Copy link

I'm working on a simple blob trigger azure function that writes a message to a queue (output) once a blob is placed in a specified path. When I use the extraOutput with a queue binding, the message isn't recorded.

Investigative information

Note: the queue binding is working as I have another function in the same project that writes to a queue from a http request trigger.

Note 2: [do not know if related] Comparing both functions, if the queue doesn't exist (the name specified for the queue does not matches an existing queue in Azure), the http triggered function will successfully create a queue and record the message, however the blob triggered one will create a queue with a "weird" name and do not record the message.

image

Explanation for the image:

  • First and second queue were created by the blob triggered function
  • Fourth was created by the HTTP trigger function (using the same binding output configuration)

Repro steps

queueBinding.js:

const { output } = require('@azure/functions');

const queueOutput = output.storageQueue({
        // Connections String to the storage account
        connection: 'AzureWebJobsStorage',
        // Name of the queue storage
        queueName: 'newqueue'        
});

module.exports = { queueOutput };

blobToQueue.js:

const {app} = require("@azure/functions")
const {queueOutput} = require("../bindings/queueBinding")

app.storageBlob("blobToQueue", {
    path: "files/{name}",
    connection: "AzureWebJobsStorage",
    authLevel: 'anonymous',
    extraOuptuts: [queueOutput],
    handler: async function (blob, context) {
        try{
            context.log(`Blob ${blob} processed`);
            return context.extraOutputs.set(queueOutput, ['message 1', 'message 2']);
        }catch(error){
            context.log(`Error: ${error}`);
            return error;
        }
    }
})

httpToQueue.js:

const { app, HttpResponse } = require('@azure/functions')
const { queueOutput } = require('../bindings/queueBinding');
const queue = require('../controllers/queue')

app.http('httpToQueue', {
    route: 'httpToQueue', // Route the function will be accessible. Accepts path parameters {parameter}
    methods: ['POST'], // Restricts the function to only be triggered by the specified methods
    authLevel: 'anonymous', // Defines the level of authentication required to access the function
    extraOutputs: [queueOutput], // Defines the extra outputs that will be written to the queue

    // Defines the function that will be executed when the function is triggered
    handler: async function (request, context) {

        // API versioning using query parameters
        try{
            if(!request.query.get('version')){
                context.log(`No version found in request`);
                const response = new HttpResponse({jsonBody: {message: 'Please specify `version` query parameter'}});
                response.headers.set('Content-Type', 'application/json');
                return response;
            }
            /*
            * Here you specify which controller version to use based on the version specified in the request
            */
            if(request.query.get('version') == '0'){
                return queue(request, context);
            }
            else {
                context.log(`Version ${request.query.get('version')} not found`);
                const response = new HttpResponse({jsonBody: {message: 'Please specify a correct `version` query parameter'}});
                response.headers.set('Content-Type', 'application/json');
                return response;
            }
        }catch(error){
            context.log(`Error: ${error}`);
            return error;
        }
    }
});

queue.js:

const { HttpResponse } = require('@azure/functions');
const { queueOutput } = require('../bindings/queueBinding')

async function queue (request, context) {

    try{
    /*
    * If operation is POST, write message to queue storage 
    */
       if(request.method == 'POST'){
            /*
            * Initial checks to confirm that request can be processed.
            */

            // check if request has a body
            if(!request.body){
                context.log(`No body found in request`);
                const response = new HttpResponse({jsonBody: {message: 'No body found'}});
                response.headers.set('Content-Type', 'application/json');
                return response;
            }
            const body = await request.json() || null;
            const { message } = body;

            // Check if the request has a message
            if(!message){
                context.log(`No message found in request body`);
                const response = new HttpResponse ({jsonBody: {message: 'Please specify `message` key inside JSON payload'}});
                return response;
            }

            context.log(`Http function processed request for url "${request.url}" for method "${request.method}"`);
            // Process request by writting the content of the request to a storage queue
            context.extraOutputs.set(queueOutput, message);
            const response = new HttpResponse({jsonBody: {message: `Message registered successfully`}});
            response.headers.set('Content-Type', 'application/json');
            return response;
        }

        
    }catch( error) {
        context.log(`Error: ${error}`);
        return error;
    }


    }

module.exports = queue;
@ejizba
Copy link
Contributor

ejizba commented Oct 27, 2023

Hi @LuwkasLima thanks for the detailed report including code! I tried it out and I think your problem is a mispelling 😅 Check "extraOuptuts" in "blobToQueue.js"

@ejizba ejizba closed this as not planned Won't fix, can't repro, duplicate, stale Nov 2, 2023
@LuwkasLima
Copy link
Author

Hi @ejizba, thanks for looking at the issue, I appreciate your time. I would have mixed feelings if that was the solution (such a simple one!), but unfortunately it was just a typo introduced when reporting the issue. Here more details of a second test I performed:

(working function) blobToQueue.js:

const { app } = require("@azure/functions")

const {queueOutput} = require("../bindings/queueBinding")

app.storageBlob("blobToQueue", {
    path: "blob/{name}", // This is the path to the blob that will trigger the function
    connection: "AzureWebJobsStorage", // This is the connection string to the blob storage
    return: queueOutput, // This is the binding that will be used to write to the message queue storage
    handler: async function (blob, context) {
        const data = [
            "Message 1",
            "Message 2"
        ]

        return data;
    }
})

(do not work) blobToQueueExtra.js:

const { app } = require("@azure/functions");

const { queueOutput } = require("../bindings/queueBinding");

app.storageBlob("blobToQueueExtra", {
  path: "blob/{name}", // This is the path to the blob that will trigger the function
  connection: "AzureWebJobsStorage", // This is the connection string to the blob storage
  extraOutputs: [queueOutput], // Defines the extra outputs that will be written to the message queue storage - currently not working (issue: https://github.com/Azure/azure-functions-nodejs-library/issues/179)
  handler: async function (blob, context) {
    const data = [
      "Message 1",
      "Message 2"
  ]
    return context.extraOutputs.set(queueOutput, data);
  },
});

Note: I thought perhaps that extraOutputs wasn't handling arrays, so I changed data to a simple string, but it also do not writes to the queue message.

queueBinding.js - working for another http trigger function:

const { output } = require('@azure/functions');

const queueOutput = output.storageQueue({
        // Connections String to the storage account
        connection: 'AzureWebJobsStorage',
        // Name of the queue storage
        queueName: '%input_queue_name%' // NOTE: Define queueName on Application Settings in Azure Functions. If messaging queue does not exist, it will be created.
});

module.exports = { queueOutput };

@ejizba ejizba reopened this Nov 10, 2023
@ejizba ejizba added the bug Something isn't working label Nov 10, 2023
@ejizba
Copy link
Contributor

ejizba commented Nov 10, 2023

@LuwkasLima thanks for the detailed info. I think I've found the bug on our side. It only happens if you share an output object between two functions that differ on using return vs extraOutputs. So you could workaround this by:

  1. Consistently using either return or extraOutputs
  2. Defining a second queueOutput instead of sharing it

@ejizba ejizba self-assigned this Nov 10, 2023
@LuwkasLima
Copy link
Author

@ejizba I'll respectfully challenge that assumption, due to the fact that I have tested only one function at a time (no sharing of output objects).

I've also observed the same issue happening for a Message Queue triggered function writing an output to a Table storage. When using the extraOutput, the output isn't written to the Table entity (I can share details of that scenario in another issue if you would like)

Once more, thanks for looking at that.

@ejizba
Copy link
Contributor

ejizba commented Nov 10, 2023

Well "blobToQueueExtra.js" works perfectly fine for me by itself, but I can reproduce the problem if I also have "blobToQueue.js" in my app. I know what the problem is in this case and I already have a fix, so we found a bug regardless of if it was your bug.

You're welcome to wait and see if my fix helps you (maybe I just didn't explain it correctly) or give more details on your scenario

@LuwkasLima
Copy link
Author

Got it. You have graciously shut down my arguments. =)

In fact, I do have both in my code. I was just running them, which I thought was in "isolation", locally using the parameters at local.settings.json.

Thx again for looking at it and for the proposed workaround. I hope this serves as a source for others to quickly get things done.

@ejizba ejizba added this to the November 2023 milestone Nov 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants