Using Serverless Framework Environment Variables .net core Lambda

AWS Serverless .net core

In previous posts, have explored getting started with Serverless Framework and how to implement Simple Dependency Injection and then we explored how to implement more advanced dependency injection concepts relating to AWS Lambda.

In each of these posts, we explored different levels of interactivity and how the Serverless Framework helps you develop and deploy your AWS Lambda functions and .net core and C#. In this post, we are going to delve a little deeper again into one specific area of how you can create Environment Variables with the Serverless Framework and share them with the .net core executable.

This is a useful strategy to learn as it helps you to create easily configurable and maintainable infrastructure as code and helps to element those Magic Strings from your code base.

We are going to make use of the serverless.yml to create our environment variables so if you not all that familiar with it then I recommend reading the high-level serverless.yml reference but I will explain the features we’re going to use as I go through.

Environment Variables in Serverless Framework

The Environment tag in the Serverless Framework YAML file enables you to create service-wide environment variables which you can easily make use of in other programming languages, for instance in nodejs based lambda functions you could simply create an environment variable in your serverless.yml as follows

Environment:
    Variables:
      TABLE_NAME: SomeTableName

You can then simply reference and get the value from the environment variable in your code as follows

const tableName = process.env.TABLE_NAME;

This is because of one of the many quirks of the JavaScript that make it both a pleasure and pain to work with in equal amounts and also because the process object is a global that provides information about, and control over, the current Node.js process

In .net core however there is a little bit additional work required but, in my opinion at least, is worth it when it comes to managing variables etc.

We will be building on the code base I introduced in how to implement Simple Dependency Injection the branch we’ll be using is EnvironmentVariables

How to use Environment Variables in .net core

In order to extend our AWS Lambda application to make use of Environment Variables we will need to add an additional reference to our application.

dotnet add package Microsoft.Extensions.Configuration.EnvironmentVariables --version 2.1.0

Once this is added we can now add an environment variable to our serverless.yml. We’ll create an environment variable called SaySomething and provide some arbitary statement.

service: Hello-Configuration

provider:
  name: aws
  runtime: dotnetcore2.1
  environment:
    SaySomething: "Eating coconuts is cool"
  
package:
  individually: true

functions:
  hello:
    handler: HelloConfiguration::Threenine.ConfigTest.Functions.Speak::Greet


    package:
      artifact: bin/release/netcoreapp2.1/hello.zip

We can then edit our LambdaConfiguration.cs to the below

using System.IO;
using Microsoft.Extensions.Configuration;

namespace Threenine.ConfigTest
{
    public class LambdaConfiguration : ILambdaConfiguration
    {
        public static IConfigurationRoot Configuration => new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddEnvironmentVariables()
            .Build();

        IConfigurationRoot ILambdaConfiguration.Configuration => Configuration;
    }
}

We can now make use of this in our code as follows, we’ll edit our SpeakService to make use of the of the environment variable.

using Microsoft.Extensions.Options;

namespace Threenine.ConfigTest.Services
{
    public class SpeakService : ISpeakService
    {
        private readonly Greeting _greeting;
      
        public SpeakService(IOptions<Greeting> hello)
        {
            _greeting = hello.Value;
           
        }

        public string Greeting => string.Concat( _greeting.Message, " ", System.Environment.GetEnvironmentVariable("SaySomething"));
    }
}

If we build, deploy and invoke our lambda this will work fine. However, I’m sure the clean coders amongst you will be thinking that this is dirty and we have polluted the code base a little.

Adding Environment Variables to Dependency Injection

Next we’ll create a class that we will use to store the environment variable, we will make available to the classes in which will make use of it via dependency injection.

In this case it’s a very simple class which is going t have a property we will make use of later.

namespace Threenine.ConfigTest.Config
{
    public class SpeakEnvironment
    {
        public string SaySomething { get; set; }
    }
}

The .net core framework enables us to really easily now configure and bind these variables at runtime using the Environment Variables Configuration Provider , all that we now really do is rename our Environment Variable in our serverless.yaml to include the name of our Class and a double underscore

When working with hierarchical keys in environment variables, a colon separator (:) may not work on all platforms (for example, Bash). A double underscore (__) is supported by all platforms and is replaced by a colon.

service: Hello-Configuration

provider:
  name: aws
  runtime: dotnetcore2.1
  environment:
    SpeakEnvironment__SaySomething: "Eating coconuts is cool"
  
package:
  individually: true

functions:
  hello:
    handler: HelloConfiguration::Threenine.ConfigTest.Functions.Speak::Greet


    package:
      artifact: bin/release/netcoreapp2.1/hello.zip

Modify the StartUp class to configure the environment variables and wire them up for Dependency Injection we do this on line 20, which is similar to what we did previously.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Threenine.ConfigTest.Config;
using Threenine.ConfigTest.Services;

namespace Threenine.ConfigTest
{
    public class StartUp
    {
         public static IServiceCollection Container => ConfigureServices(LambdaConfiguration.Configuration); 
                
        
                private static IServiceCollection ConfigureServices(IConfigurationRoot root)
                {
                    var services = new ServiceCollection();
                    services.Configure<Greeting>(options =>
                        root.GetSection("greeting").Bind(options));
                    
                    services.AddTransient<ISpeakService, SpeakService>();
                    services.Configure<SpeakEnvironment>(options => root.GetSection("SpeakEnvironment").Bind(options));
                   return services;
        
                }
    }
}

We can edit our Speak Service to include the injected options.

using Microsoft.Extensions.Options;
using Threenine.ConfigTest.Config;

namespace Threenine.ConfigTest.Services
{
    public class SpeakService : ISpeakService
    {
        private readonly Greeting _greeting;
        private readonly SpeakEnvironment _speakEnvironment;
      
        public SpeakService(IOptions<Greeting> hello, IOptions<SpeakEnvironment> speakEnvironment)
        {
            _greeting = hello.Value;
            _speakEnvironment = speakEnvironment.Value;

        }

        public string Greeting => string.Concat( _greeting.Message, "  ", _speakEnvironment.SaySomething);
    }
}

Additional Configuration Options

The above example works great, however you may have still spotted an additional concern. Perhaps you’re thinking this is great but what if you wanted to have different configuration options for your specific environments. For instance what if you wanted data that is created in your staging environment be redirected to different tables or queues than the ones you use in the Production ?

Fortunately, the Serverless Framework provides a super simple option, a configuration for your serverless.yml. By convention, this is typically implemented by creating an additional env.configs.yml

Typically you’ll use this file to create Environment Specific configuration information or sensitive information. For instance, you may want to store your AWS Account ID or even version or feature information.

This file will most likely not be stored in your Source Code Repository and will probably be managed by your DevOps process. However, in my case I included it in the repo to provide a sample.

I’ll create a simple file to provide a general idea:

feature: <feature_name>
version: 1.0.0.0
region: <aws_region>
accountId: <aws_account_id>
something: "Coconuts are cool to eat"

In our case we created a variable which we name something and provided an arbitrary value. We can now edit our serverless.yml to make use of this file

service: Hello-Configuration

provider:
  name: aws
  runtime: dotnetcore2.1
  environment:
    SpeakEnvironment__SaySomething: ${file(env.configs.yml):something}
  
package:
  individually: true

functions:
  hello:
    handler: HelloConfiguration::Threenine.ConfigTest.Functions.Speak::Greet

    package:
      artifact: bin/release/netcoreapp2.1/hello.zip

We can simply deploy our lambda invoke it and notice really that nothing has changed in the execution but our arbitrary statement now comes from our config file.

Summary

We now have an easy to configure Lambda and the ability to create environment variables within our Serverless.yaml and consume them in our .net core lambda.

Gary Woodfine

Freelance Full Stack Developer at threenine.co.uk
Helps businesses by improving their technical proficiencies and eliminating waste from the software development pipelines.

A unique background as business owner, marketing, software development and business development ensures that he can offer the optimum business consultancy services across a wide spectrum of business challenges.