Skip to content

Invoke Local: Automatically add environment variables from yaml file#7147

Closed
nkeating-mutualofenumclaw wants to merge 53 commits into
serverless:masterfrom
mutual-of-enumclaw:master
Closed

Invoke Local: Automatically add environment variables from yaml file#7147
nkeating-mutualofenumclaw wants to merge 53 commits into
serverless:masterfrom
mutual-of-enumclaw:master

Conversation

@nkeating-mutualofenumclaw

@nkeating-mutualofenumclaw nkeating-mutualofenumclaw commented Dec 26, 2019

Copy link
Copy Markdown
Contributor

…rless.yml file to the invoke environment

What did you implement

Currently when invoking a function locally, the environment variables from the serverless.yml are not automatically included in the runtime environment for the function. This update puts the environment variables for the serverless.yml provider and functions as the default values, which can be overwritten by the command line parameters. This reduces the amount of work needed when dealing with hard coded environment variables in the serverless yml file.

Addresses #7087

How can we verify it

Run the tests, or:

# serverless.yml
...
functions:
   test:
      handler: test.handler
      environment:
         foo: test
...
// test.js
...
modules.exports.handler = () => {
   console.log(process.env.foo);
}
...

Todos

N/A

  • Write and run all tests
  • Write documentation
  • Enable "Allow edits from maintainers" for this PR
  • Update the messages below

Is this ready for review?: YES
Is it a breaking change?: NO

@pmuens pmuens left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey @nkeating-mutualofenumclaw thanks for working on this! 👍

I just tested the current master with the attached serverless.yml and handler.js file and couldn't reproduce the problem. I ran invoke local with and without the --docker flag.

What runtime are you using? Could you paste the serverless.yml file you used when you ran into the problem?

Thanks in advance!

service: test-${self:custom.idx}

provider:
  name: aws
  runtime: nodejs10.x
  versionFunctions: false
  region: eu-central-1
  stage: dev
  environment:
    PROVIDER: ${self:provider.name}
    REGION: ${self:provider.region}

custom:
  idx: 0

functions:
  hello:
    handler: functions/handler.handler
    environment:
      HANDLER: ${self:functions.hello.handler}
      IDX: ${self:custom.idx}
'use strict';

module.exports.handler = async event => {
  console.log(process.env);

  return {
    headers: {
      'Access-Control-Allow-Origin': '*', // Required for CORS support to work
      'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS
    },
    statusCode: 200,
    body: JSON.stringify(
      {
        message: 'Hello World!',
        input: event,
      },
      null,
      2
    ),
  };
};

@nkeating-mutualofenumclaw

Copy link
Copy Markdown
Contributor Author

Our serverless files we use get pretty complex which is why we thought the setting environment variables weren't working. Your example works without the modifications up until you start referencing AWS resources either through Ref, GetAtt or ImportValue. Looking at the existing serverless framework I think I found where the environment variables are currently being set. "lib/plugins/aws/invokeLocal/index.js". I'm thinking that I should remove that code I sent, and instead add an update to the previously mentioned file to interrogate the existing cloud formation deployment if it exists and translate those values.

Modified Example:
service: philipp-test-${self:custom.idx}

provider:
name: aws
runtime: nodejs10.x
versionFunctions: false
region: eu-central-1
stage: dev

environment:
PROVIDER: ${self:provider.name}
REGION: ${self:provider.region}
Test:
Ref: s3Bucket

custom:
idx: 0

functions:
hello:
handler: functions/handler.handler
environment:
HANDLER: ${self:functions.hello.handler}
IDX: ${self:custom.idx}

resources:
Resources:
s3Bucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: Private
BucketName: test

Issue with output:
Test: '[object Object]'

@nkeating-mutualofenumclaw

Copy link
Copy Markdown
Contributor Author

I've reverted the original modifications and made changes to get referenced and imported resources for SNS, SQS, DynamoDB, Lambda and ImportValue.

Here's the test yml I am using. To test import you will need to comment the reference to the export in the environment variables, deploy, then un-comment. Typically ImportValue would be used to get values from other cloudformation stacks.

service: philipp-test-${self:custom.idx}

provider:
name: aws
runtime: nodejs10.x
versionFunctions: false
region: eu-central-1
stage: dev

environment:
PROVIDER: ${self:provider.name}
REGION: ${self:provider.region}
Test:
Fn::GetAtt:
- dynamoDB
- Arn
snsTest:
Ref: sns
snsTestName:
Fn::GetAtt:
- sns
- TopicName
sqsQueueRef:
Ref: sqsQueue
sqsQueueArn:
Fn::GetAtt:
- sqsQueue
- Arn
sqsQueueName:
Fn::GetAtt:
- sqsQueue
- QueueName
imported:
Fn::ImportValue: ${self:service}-${self:provider.stage}-sqs

custom:
idx: 0

functions:
hello:
handler: functions/handler.handler
environment:
HANDLER: ${self:functions.hello.handler}
IDX: ${self:custom.idx}
refLambdaName:
Ref: Hello2LambdaFunction
refLambdaArn:
Fn::GetAtt:
- Hello2LambdaFunction
- Arn

hello2:
handler: functions/handler2.handler
environment:
HANDLER: ${self:functions.hello.handler}
IDX: ${self:custom.idx}

resources:
Resources:
dynamoDB:
Type: AWS::DynamoDB::Table
Properties:
TableName: test
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: test
AttributeType: S
KeySchema:
- AttributeName: test
KeyType: HASH

sns:
  Type: AWS::SNS::Topic
  Properties: 
    TopicName: test-sns

sqsQueue:
  Type: AWS::SQS::Queue
  Properties:
    QueueName: TestQueue

Outputs:
test:
Value:
Ref: sqsQueue
Export:
Name: ${self:service}-${self:provider.stage}-sqs

@medikoo medikoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nkeating-mutualofenumclaw Great thanks for this effort. If I read correctly, this looks as an attempt to solve: #7087

Still as I investigated the problem, without good AWS documentation there's no way to provide a reliable resource object to string resolution, and probably best solution (to unify resolution for every case for which it happens) is to reject resource object to string resolution in any case. I elaborated a bit more on that here: #7087 (comment)

Solution proposed here looks very use case specific (e.g. Ref or Fn::GetAtt instructions are blindly assumed to reference dynamo table). I don't think it can work well in generic framework logic, it's likely to fail for many other cases.

@nkeating-mutualofenumclaw

Copy link
Copy Markdown
Contributor Author

Hi Mariusz,

That statement isn't quite accurate. Due to the way AWS operates as a consordium of individual teams, there isn't a single definition of Ref vs GetAtt. At the same time it is well documented what should be returned for each. The apis for each also have quite a bit of variation to them, making it hard to make a single function perform the value retrieval operation. This is why in the framework the conversion is broken down per service rather than trying to consolidate it all into a single function.

Reference Documentation:
DynamoDB - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html#aws-resource-dynamodb-table-return-values
SNS - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sns-topic.html#aws-properties-sns-topic-return-values
SQS - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sqs-queues.html#aws-properties-sqs-queues-return-values
Lambda - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#aws-resource-lambda-function-return-values
S3 - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html#aws-properties-s3-bucket-return-values

The "lib/plugins/aws/invokeLocal/resolvers/index.js" file creates a registration process per handler as can be seen on lines 4 - 10. This is then used if a "Ref", "GetAtt" or "ImportValue" is used in the serverless yaml for the environment variables. As the values are retrieved they are cached in the "./.serverless" directory, which during a deployment will get removed as the cache might be invalid after the deployment.

While there are more services than just the 5 I implemented, these 5 are the most commonly used with serverless AWS implementations. For any project which is significant, the ability to leverage references rather than hard coded values becomes critical, and there for local debugging without this with serverless framework without this capability is useless.

With respect, as this is a primitive capability of AWS in the same way cloudformation is, this should be a framework level component as your user base shouldn't need to add a custom plugin just to be able to debug their basic stack functions.

@medikoo

medikoo commented Jan 7, 2020

Copy link
Copy Markdown
Contributor

@nkeating-mutualofenumclaw great thanks for further clarifications.

Indeed I was mislead by AWS support, I contacted them back on that, and they've responded:

I see now that I misunderstood your question in by the previous case note. As you have rightly pointed out, the values returned by intrinsic functions are documented in "Return Values -->Ref" for each resource respectively.

In light of that, indeed it makes sense to implement resolution for popular cases, and eventually add resoulution for others on the go, whenever demand appears.

Let me review the PR

@medikoo medikoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nkeating-mutualofenumclaw great thanks for looking into that!

Implementation direction looks very good. I've suggested some improvements, also please ensure proposed changes are free from lint errors and that code matches Prettier formatting.

Thank you!

Comment thread lib/plugins/aws/invokeLocal/index.js Outdated
Comment thread lib/plugins/aws/invokeLocal/index.js Outdated
Comment thread lib/plugins/aws/invokeLocal/resolvers/index.js Outdated
Comment thread lib/plugins/aws/invokeLocal/resolvers/index.js Outdated
Comment thread lib/plugins/aws/invokeLocal/resolvers/index.js Outdated
Comment thread lib/plugins/aws/invokeLocal/resolvers/index.js Outdated
Comment thread lib/plugins/aws/invokeLocal/resolvers/index.js Outdated
Comment thread lib/plugins/aws/invokeLocal/resolvers/index.js Outdated
Comment thread lib/plugins/aws/invokeLocal/resolvers/lambda.js Outdated
return action;
}
return func.Configuration.FunctionArn;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should crash on any non Arn parameter.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm wondering if we should crash, or just return the current value and let the deployment crash. Crashing debugging on a get attribute parameter seems like it would cause issues if GetAtt is extended in cloudformation. Not setting the value at least lets the user debug, but might not set the value correctly. Not exactly sure how to solve this one... maybe log that the attribute name is not supported?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would meaningfully crash, as otherwise we're clearly be providing an incomplete environment for local invocation

@nkeating-mutualofenumclaw

Copy link
Copy Markdown
Contributor Author

I've implemented the changes suggested. Let me know what you think

@medikoo medikoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you @nkeating-mutualofenumclaw !

Can you ensure CI passes, and that it's only feature related files that are committed in with this proposal (?) Currently I see some unrelated changes, in form of some IDE related files being added, and eslint config changes, which should not be introduced here.

Comment thread .vscode/launch.json Outdated
@nkeating-mutualofenumclaw

Copy link
Copy Markdown
Contributor Author

I have on multiple times done what you had asked with more and new changes coming in the next review. I no longer have time to continue this back and forth, as I have other responsibilities.

At this point the updates work and improve the serverless framework beyond what it is currently capable of doing. Your other customers are asking when this update will be available to them. If there are more changes you'd like to make or try, you should feel free to make them. If you'd like to recreate the whole resolution code base feel free to.

@medikoo

medikoo commented Jan 23, 2020

Copy link
Copy Markdown
Contributor

@nkeating-mutualofenumclaw great thanks for this effort! It's a really worthwhile contribution, and I'm pretty sure many will appreciate it when it lands.

We'll try to do our best to finish it whenever we have some free resources for that.
As it's uncertain when it'll be the case, I'll mark it for now with help wanted badge.

If anyone wants to jump in, and finish this PR, it's very appreciated!

@nj-io

nj-io commented Mar 27, 2020

Copy link
Copy Markdown

What's happening with this change?

@medikoo

medikoo commented Mar 27, 2020

Copy link
Copy Markdown
Contributor

@ionush we're looking for help, it's waiting for someone to take it over and finalize.

@PierrickLozach

Copy link
Copy Markdown

Can the pull request from @medikoo be merged?

@corbfon

corbfon commented Apr 19, 2020

Copy link
Copy Markdown

Also interested in this change

@medikoo

medikoo commented Apr 23, 2020

Copy link
Copy Markdown
Contributor

@PierrickI3 @corbfon we're looking for help in finalizing that pull request. Are you in for that?

@PierrickLozach

Copy link
Copy Markdown

@medikoo In a project right now until the end of July and requires me to work in the evenings too. Not sure how much time I will be able to spare.

@corbfon

corbfon commented Apr 28, 2020

Copy link
Copy Markdown

@medikoo I could probably take a look later this week

@medikoo

medikoo commented Apr 28, 2020

Copy link
Copy Markdown
Contributor

@medikoo I could probably take a look later this week

That'll be great, thanks @corbfon


const cachePath = path.join(cachedir('serverless'), 'invokeLocal');

const envVarResolvers = {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@nkeating-mutualofenumclaw it would be great if you could add kinesis resolver as well.

@herebebogans

Copy link
Copy Markdown
Contributor

Is anyone working on this PR?

@medikoo

medikoo commented Jul 24, 2020

Copy link
Copy Markdown
Contributor

@herebebogans I don't think anyone is. Do you want to finish it?

@herebebogans

herebebogans commented Jul 24, 2020

Copy link
Copy Markdown
Contributor

The problem with AWS Cloudformation intrinsic functions not being resolvable via environment variables when running locally (either via invoke local or serverless-offline) has come up many times and I've contributed to a few of those discussions.

My thinking is that this is better left for plugins to solve otherwise the invokeLocal resolver module being created in this PR will need to be continually be updated as AWS update Cloudformation with new services.

Theres a few plugins that already deal with this issue - though they have some limitations.

The author of this PR has

https://github.com/mutual-of-enumclaw/serverless-plugin-local-env

There is also.

https://www.npmjs.com/package/serverless-export-env

Perhaps the documentation for serverless invoke local should describe the issue and point to these plugins?

@medikoo

medikoo commented Jul 24, 2020

Copy link
Copy Markdown
Contributor

Perhaps the documentation for serverless invoke local should describe the issue and point to these plugins?

We should probably ensure unified behavior, and crash if we find that it can't be meet, so:

  • Do not leak local env vars into invocation
  • Abort local invocation with an error if function has configured env vars that require resolution via external service. (then we can also provide a hint pointing to plugins)

@herebebogans

Copy link
Copy Markdown
Contributor

@nkeating-mutualofenumclaw

I'm guessing you raised this PR as it was difficult to implement this in a plugin? Like to get your input as you've put in a lot of work on it.

@evgenykireev

Copy link
Copy Markdown

Perhaps the documentation for serverless invoke local should describe the issue and point to these plugins?

@herebebogans My opinion on this is that it's serverless itself should include these env variables, not a plugin. The ultimate goal is that invokeLocal should behave as close to cloud execution as possible. This means if a lambda function in AWS gets a variable, it should get the same variable locally.
I reckon the main reason why it was previously implemented as a plugin is that, as we've seen with this PR, it takes months of effort to get a change like this to get merged into serverless.

@herebebogans

herebebogans commented Jul 27, 2020

Copy link
Copy Markdown
Contributor

@herebebogans My opinion on this is that it's serverless itself should include these env variables, not a plugin. The ultimate goal is that invokeLocal should behave as close to cloud execution as possible. This means if a lambda function in AWS gets a variable, it should get the same variable locally.

I can understand how the surprise of [object Object] as an environment variable impacts those of us who like to develop locally but refer to AWS resources in a consistent way as happens in the lambda runtime.

Supporting the most commonly used resources ie .. S3, Dynamo , SQS etc as is being done in this PR would definitely help , however for this feature to be complete you would need to support all Cloudformation Resources which is a large initial and continous ongoing effort.

In addition do you draw the line only supporting the common cloudformation return types ie !Ref, !GetAtt , !ImportValue etc, or would this resolver need to handle all CF intrinsic functions? eg something like

!FnIf ['mycondition', !Ref ThisS3Bucket, !Ref ThatS3Bucket]

There's a larger issue already in serverless with CF intrinsic functions - they are supported and implemented differently across serverless. For example I can use !ImportValue in a sqs event definition but not in a kinesis event definition or for the provider.deployment bucket property .. Why?

I think adding more of this only adds to the problem?

@nkeating-mutualofenumclaw

Copy link
Copy Markdown
Contributor Author

Eventually all the scenarios should be covered. The problem is without a base of support, even around referenced variables, the framework itself starts becoming less useful for development purposes. This becomes even more stark when its the Solution Architect that chooses and typically writes the serverless file, and developers don't have the ability to do what other frameworks (like SAM) have baked in to their ability to debug.

The FnIf example isn't that hard to implement as an additional feature, nor are other cloudformation functions. Trying to solve everything in one go though is setting this process up to be a long drawn out process, when a more iterative process would allow this feature to be added and improved on much more quickly.

@herebebogans

Copy link
Copy Markdown
Contributor

All good points, you've convinced me to finalise this PR ..

@PierrickLozach

Copy link
Copy Markdown

@herebebogans, any luck? This would be really useful

@herebebogans

herebebogans commented Sep 1, 2020

Copy link
Copy Markdown
Contributor

I had a look through the PR and started extending it to support more resources it but it's solely focused on supporting invoke local. I think the majority of users that want this feature are really wanting it to work with both serverless-offline and invoke local .

@medikoo can you think of a more general purpose way this can be solved in the framework to satisfy both.

@medikoo

medikoo commented Sep 1, 2020

Copy link
Copy Markdown
Contributor

@medikoo can you think of a more general purpose way this can be solved in the framework to satisfy both.

if you mean that resolution of env vars should be done in a way that serves both local invocation and eventual plugins. Then I think it can be achieved by secluding the resolution logic into serverless.service.resolveFunctionEnvVars(functionName) method which then could be used by both.

What do you think?

@medikoo

medikoo commented Sep 9, 2020

Copy link
Copy Markdown
Contributor

@nkeating-mutualofenumclaw great thanks for the effort! I'm going to close this as this was partially addressed by #8157 (added support for Fn::ImportValue resolution) and #8198 (added support for Ref resolution) and at current stage probably this branch has derived too far from master to continue work on it.

What's missing (and what was supposed to be covered with this PR) is support to resolve Fn::GetAtt. We're open for PR's that'll bring support for that function specifically.

@medikoo medikoo closed this Sep 9, 2020
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 28.75817% with 109 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.08%. Comparing base (526b21b) to head (cdf318b).

Files with missing lines Patch % Lines
lib/plugins/aws/invokeLocal/index.js 37.20% 54 Missing ⚠️
lib/plugins/aws/invokeLocal/resolvers/sqsQueue.js 13.33% 13 Missing ⚠️
.../plugins/aws/invokeLocal/resolvers/ssmParameter.js 8.33% 11 Missing ⚠️
...invokeLocal/resolvers/stepFunctionsStateMachine.js 11.11% 8 Missing ⚠️
...plugins/aws/invokeLocal/resolvers/dynamoDbTable.js 22.22% 7 Missing ⚠️
...lugins/aws/invokeLocal/resolvers/lambdaFunction.js 25.00% 6 Missing ⚠️
lib/plugins/aws/invokeLocal/resolvers/s3Bucket.js 25.00% 6 Missing ⚠️
lib/plugins/aws/invokeLocal/resolvers/snsTopic.js 33.33% 4 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #7147      +/-   ##
==========================================
- Coverage   88.14%   87.08%   -1.06%     
==========================================
  Files         236      243       +7     
  Lines        8640     8776     +136     
==========================================
+ Hits         7616     7643      +27     
- Misses       1024     1133     +109     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.