Repository: serverless/serverless (JavaScript) — Feature

Issue:
AWS_PROFILE not exported to environment for invoke local
# This is a Feature Proposal (Arguably a bug report)

## Description

I would very much like it if `AWS_PROFILE` behaved like `AWS_REGION` for `invoke local`. The fact that region is supplied from my configuration is nice and expected, and it is surprising and annoying that profile is not. It means I don't have to set `AWS_REGION` before doing local invoke but I always have to keep setting `AWS_PROFILE`. These should behave orthogonally in my opinion.

Personally I believe this is a bug but I can understand others may not. Especially if they aren't juggling different profiles for different projects.

As I reported on Slack:
in the case where you do `invoke` with region specified in provider, it uses the region from provider
in the case where you do `invoke local` with region specified in provider, it uses the region from provider
in the case where you do `invoke` with profile specified in provider, it uses the profile from provider
in the case where you do `invoke local` with profile specified in provider, it *does not use* the profile from provider. this is surprising.




Discussion:

Resolving patch:
diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js
index 51547b2a8ea..1d5adaf2305 100644
--- a/lib/plugins/aws/invokeLocal/index.js
+++ b/lib/plugins/aws/invokeLocal/index.js
@@ -111,6 +111,12 @@ class AwsInvokeLocal {
       NODE_PATH: '/var/runtime:/var/task:/var/runtime/node_modules',
     };
 
+    // profile override from config
+    const profileOverride = this.provider.getProfile();
+    if (profileOverride) {
+      lambdaDefaultEnvVars.AWS_PROFILE = profileOverride;
+    }
+
     const providerEnvVars = this.serverless.service.provider.environment || {};
     const functionEnvVars = this.options.functionObj.environment || {};
 
diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js
index cb68ddcd898..95743d1c6c0 100644
--- a/lib/plugins/aws/provider/awsProvider.js
+++ b/lib/plugins/aws/provider/awsProvider.js
@@ -419,6 +419,19 @@ class AwsProvider {
     return regionSourceValue.value || defaultRegion;
   }
 
+  getProfileSourceValue() {
+    const values = this.getValues(this, [
+      ['options', 'profile'],
+      ['serverless', 'config', 'profile'],
+      ['serverless', 'service', 'provider', 'profile'],
+    ]);
+    const firstVal = this.firstValue(values);
+    return firstVal ? firstVal.value : null;
+  }
+  getProfile() {
+    return this.getProfileSourceValue();
+  }
+
   getServerlessDeploymentBucketName() {
     if (this.serverless.service.provider.deploymentBucket) {
       return BbPromise.resolve(this.serverless.service.provider.deploymentBucket);


Test patch:
diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js
index df39eb7faed..4bd83d751b1 100644
--- a/lib/plugins/aws/invokeLocal/index.test.js
+++ b/lib/plugins/aws/invokeLocal/index.test.js
@@ -24,6 +24,7 @@ describe('AwsInvokeLocal', () => {
   let serverless;
   let provider;
   let awsInvokeLocal;
+
   beforeEach(() => {
     options = {
       stage: 'dev',
@@ -266,12 +267,23 @@ describe('AwsInvokeLocal', () => {
       };
     });
 
+    afterEach(() => {
+      delete process.env.AWS_PROFILE;
+    });
+
     it('it should load provider env vars', () => awsInvokeLocal
       .loadEnvVars().then(() => {
         expect(process.env.providerVar).to.be.equal('providerValue');
       })
     );
 
+    it('it should load provider profile env', () => {
+      serverless.service.provider.profile = 'jdoe';
+      return awsInvokeLocal.loadEnvVars().then(() => {
+        expect(process.env.AWS_PROFILE).to.be.equal('jdoe');
+      });
+    });
+
     it('it should load function env vars', () => awsInvokeLocal
       .loadEnvVars().then(() => {
         expect(process.env.functionVar).to.be.equal('functionValue');
diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js
index d3ce0ee9227..0aa90b83f56 100644
--- a/lib/plugins/aws/provider/awsProvider.test.js
+++ b/lib/plugins/aws/provider/awsProvider.test.js
@@ -337,7 +337,6 @@ describe('AwsProvider', () => {
           expect(awsProvider.getCredentials()).to.deep.eql({ region: options.region });
         });
     });
-
     it('should retry if error code is 429', (done) => {
       const error = {
         statusCode: 429,
@@ -991,6 +990,46 @@ describe('AwsProvider', () => {
     });
   });
 
+  describe('#getProfile()', () => {
+    let newAwsProvider;
+
+    it('should prefer options over config or provider', () => {
+      const newOptions = {
+        profile: 'optionsProfile',
+      };
+      const config = {
+        profile: 'configProfile',
+      };
+      serverless = new Serverless(config);
+      serverless.service.provider.profile = 'providerProfile';
+      newAwsProvider = new AwsProvider(serverless, newOptions);
+
+      expect(newAwsProvider.getProfile()).to.equal(newOptions.profile);
+    });
+
+    it('should prefer config over provider in lieu of options', () => {
+      const newOptions = {};
+      const config = {
+        profile: 'configProfile',
+      };
+      serverless = new Serverless(config);
+      serverless.service.provider.profile = 'providerProfile';
+      newAwsProvider = new AwsProvider(serverless, newOptions);
+
+      expect(newAwsProvider.getProfile()).to.equal(config.profile);
+    });
+
+    it('should use provider in lieu of options and config', () => {
+      const newOptions = {};
+      const config = {};
+      serverless = new Serverless(config);
+      serverless.service.provider.profile = 'providerProfile';
+      newAwsProvider = new AwsProvider(serverless, newOptions);
+
+      expect(newAwsProvider.getProfile()).to.equal(serverless.service.provider.profile);
+    });
+  });
+
   describe('#getServerlessDeploymentBucketName()', () => {
     it('should return the name of the serverless deployment bucket', () => {
       const describeStackResourcesStub = sinon