Repository: logandk/serverless-wsgi (language: python)

Issue:
Provide function to execute when calling sls wsgi command
We run django in AWS Lambda and want to apply database migrations via `sls wsgi manage`. The database user the application runs with is limited and not allowed to create/alter tables. Thus we want to define an alternate Lambda function with different environment variables providing database admin credentials. This is primarily a security concern to segregate the database base user the application uses from the database user the migration script uses.

`findHandler` currently returns the first function with condition `handler: wsgi_handler.handler`.

https://github.com/logandk/serverless-wsgi/blob/master/index.js#L436C7-L440

Instead of returning the first function, if `--function / -f (required)` is specified, the specified function should be used. 


Discussion / hints:
Hey @logandk,

I'm interested in contributing to this issue. I have few questions:

1. Are there any specific guidelines I should follow?
2. Is there existing documentation or resources you recommend?
3. Are there any particular challenges I should be aware of?

Thank you for your help!

Hi @ayaan-qadri,
Awesome, any contributions are very welcome.

1. Make sure that the linter is happy and to add test coverage for any new features you add. See the CI pipeline for details on how to run it.
2. You'll need to refer to AWS and/or Serverless documentation. For details about this library, you'll find it inline or in the README.
3. The whole library has been structured around having a single WSGI handler function, so you might need to make some fundamental changes to make this work.
@logandk Thank you for the answers. I’ll get on this right away.

Fix patch:
diff --git a/.eslintrc.js b/.eslintrc.js
index 4787cf3..469f59f 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -2,6 +2,7 @@ module.exports = {
   env: {
     es6: true,
     node: true,
+    mocha: true,
   },
   extends: "eslint:recommended",
   parserOptions: {
diff --git a/index.js b/index.js
index 3c1b279..872c75f 100644
--- a/index.js
+++ b/index.js
@@ -434,13 +434,29 @@ class ServerlessWSGI {
   }
 
   findHandler() {
-    return _.findKey(this.serverless.service.functions, (fun) =>
-      _.includes(fun.handler, "wsgi_handler.handler")
-    );
+    const functionName = this.options.function || this.options.f;
+
+    if (functionName) {
+      // If the function name is specified, return it directly
+      if (this.serverless.service.functions[functionName]) {
+        return functionName;
+      } else {
+        throw new Error(`Function "${functionName}" not found.`);
+      }
+    } else {
+      return _.findKey(this.serverless.service.functions, (fun) =>
+        _.includes(fun.handler, "wsgi_handler.handler")
+      );
+    }
   }
 
   invokeHandler(command, data, local) {
-    const handlerFunction = this.findHandler();
+    let handlerFunction;
+    try {
+      handlerFunction = this.findHandler();
+    } catch (error) {
+      return BbPromise.reject(error.message);
+    }
 
     if (!handlerFunction) {
       return BbPromise.reject(


Test patch:
diff --git a/index.test.js b/index.test.js
index 8e20b88..f324522 100644
--- a/index.test.js
+++ b/index.test.js
@@ -1545,8 +1545,8 @@ describe("serverless-wsgi", () => {
   });
 
   describe("exec", () => {
-    const mockCli = Object({log: () => {}});
-    
+    const mockCli = Object({ log: () => {} });
+
     it("fails when invoked without command or file", () => {
       var plugin = new Plugin(
         {
@@ -1681,7 +1681,7 @@ describe("serverless-wsgi", () => {
   });
 
   describe("exec local", () => {
-    const mockCli = {log: () => {}};
+    const mockCli = { log: () => {} };
 
     it("fails when invoked without command or file", () => {
       var plugin = new Plugin(
@@ -1788,7 +1788,7 @@ describe("serverless-wsgi", () => {
   });
 
   describe("command", () => {
-    const mockCli = {log: () => {}};
+    const mockCli = { log: () => {} };
 
     it("fails when no wsgi handler is set", () => {
       var plugin = new Plugin(
@@ -1915,7 +1915,7 @@ describe("serverless-wsgi", () => {
   });
 
   describe("command local", () => {
-    const mockCli = {log: () => {}};
+    const mockCli = { log: () => {} };
 
     it("fails when no wsgi handler is set", () => {
       var plugin = new Plugin(
@@ -2046,34 +2046,48 @@ describe("serverless-wsgi", () => {
   });
 
   describe("manage", () => {
-    const mockCli = Object({log: () => {}});
+    const mockCli = {
+      log: sinon.spy(),
+    };
 
-    it("calls handler to execute manage commands remotely from argument", () => {
-      var plugin = new Plugin(
+    let plugin;
+    let sandbox;
+
+    beforeEach(() => {
+      sandbox = sinon.createSandbox();
+      plugin = new Plugin(
         {
           config: { servicePath: "/tmp" },
           service: {
             provider: { runtime: "python2.7" },
             custom: { wsgi: { app: "api.app" } },
-            functions: { app: { handler: "wsgi_handler.handler" } },
+            functions: {
+              app: { handler: "wsgi_handler.handler" },
+              otherFunc: { handler: "other_handler.handler" },
+            },
           },
           classes: { Error: Error },
           cli: mockCli,
           pluginManager: {
             cliOptions: {},
-            run: (command) =>
-              new BbPromise((resolve) => {
-                expect(command).to.deep.equal(["invoke"]);
-                console.log('[0, "manage command output"]'); // eslint-disable-line no-console
-                resolve();
-              }),
+            run: sandbox.stub().resolves(),
           },
         },
         { command: "check" }
       );
+    });
+
+    afterEach(() => {
+      sandbox.restore();
+    });
+
+    it("calls handler to execute manage commands remotely from argument", () => {
+      plugin.serverless.pluginManager.run.callsFake((command) => {
+        expect(command).to.deep.equal(["invoke"]);
+        console.log('[0, "manage command output"]');
+        return BbPromise.resolve();
+      });
 
-      var sandbox = sinon.createSandbox();
-      let loggerSpy = sandbox.spy(mockCli, "log");
       return plugin.hooks["wsgi:manage:manage"]().then(() => {
         expect(plugin.serverless.pluginManager.cliOptions.f).to.equal("app");
         expect(plugin.options.function).to.equal("app");
@@ -2083,14 +2097,65 @@ describe("serverless-wsgi", () => {
         expect(plugin.options.data).to.equal(
           '{"_serverless-wsgi":{"command":"manage","data":"check"}}'
         );
-        expect(loggerSpy.calledWith("manage command output")).to.be.true;
-        sandbox.restore();
+        expect(mockCli.log.calledWith("manage command output")).to.be.true;
       });
     });
+
+    it("uses the function specified by --function", () => {
+      plugin.options.function = "otherFunc";
+
+      return plugin.hooks["wsgi:manage:manage"]().then(() => {
+        expect(plugin.serverless.pluginManager.cliOptions.f).to.equal(
+          "otherFunc"
+        );
+        expect(plugin.options.function).to.equal("otherFunc");
+      });
+    });
+
+    it("uses the function specified by -f", () => {
+      plugin.options.f = "otherFunc";
+
+      return plugin.hooks["wsgi:manage:manage"]().then(() => {
+        expect(plugin.serverless.pluginManager.cliOptions.f).to.equal(
+          "otherFunc"
+        );
+        expect(plugin.options.function).to.equal("otherFunc");
+      });
+    });
+
+    it("throws an error when specified function is not found", () => {
+      plugin.options.function = "nonExistentFunc";
+
+      return expect(plugin.hooks["wsgi:manage:manage"]()).to.be.rejectedWith(
+        'Function "nonExistentFunc" not found.'
+      );
+    });
+
+    it("falls back to finding wsgi handler when no function is specified", () => {
+      delete plugin.options.function;
+      delete plugin.options.f;
+
+      return plugin.hooks["wsgi:manage:manage"]().then(() => {
+        expect(plugin.serverless.pluginManager.cliOptions.f).to.equal("app");
+        expect(plugin.options.function).to.equal("app");
+      });
+    });
+
+    it("rejects when no wsgi handler is found and no function is specified", () => {
+      delete plugin.options.function;
+      delete plugin.options.f;
+      plugin.serverless.service.functions = {
+        someFunc: { handler: "some_handler.handler" },
+      };
+
+      return expect(plugin.hooks["wsgi:manage:manage"]()).to.be.rejectedWith(
+        "No functions were found with handler: wsgi_handler.handler"
+      );
+    });
   });
 
   describe("manage local", () => {
-    const mockCli = Object({log: () => {}});
+    const mockCli = Object({ log: () => {} });
 
     it("calls handler to execute manage commands locally from argument", () => {
       var plugin = new Plugin(
@@ -2137,7 +2202,7 @@ describe("serverless-wsgi", () => {
   });
 
   describe("flask", () => {
-    const mockCli = Object({log: () => {}});
+    const mockCli = Object({ log: () => {} });
     it("calls handler to execute flask commands remotely from argument", () => {
       var plugin = new Plugin(
         {
@@ -2180,7 +2245,7 @@ describe("serverless-wsgi", () => {
   });
 
   describe("flask local", () => {
-    const mockCli = Object({log: () => {}});
+    const mockCli = Object({ log: () => {} });
     it("calls handler to execute flask commands locally from argument", () => {
       var plugin = new Plugin(
         {