using System;
using System.Collections;
using System.IO;
using System.Collections.Generic;
using System.Text.Json;
using YamlDotNet.RepresentationModel;
namespace Amazon.Lambda.TestTool.Runtime
{
///
/// This class handles getting the configuration information from aws-lambda-tools-defaults.json file
/// and possibly a CloudFormation template. YAML CloudFormation templates aren't supported yet.
///
public static class LambdaDefaultsConfigFileParser
{
public static LambdaConfigInfo LoadFromFile(string filePath)
{
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"Lambda config file {filePath} not found");
}
var configFile = JsonSerializer.Deserialize(File.ReadAllText(filePath).Trim(), new System.Text.Json.JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
configFile.ConfigFileLocation = filePath;
return LoadFromFile(configFile);
}
public static LambdaConfigInfo LoadFromFile(LambdaConfigFile configFile)
{
var configInfo = new LambdaConfigInfo
{
AWSProfile = !string.IsNullOrEmpty(configFile.Profile) ? configFile.Profile : "default",
AWSRegion = !string.IsNullOrEmpty(configFile.Region) ? configFile.Region : null,
FunctionInfos = new List()
};
if (string.IsNullOrEmpty(configInfo.AWSProfile))
configInfo.AWSProfile = "default";
var templateFileName = !string.IsNullOrEmpty(configFile.Template) ? configFile.Template : null;
var functionHandler = !string.IsNullOrEmpty(configFile.DetermineHandler()) ? configFile.DetermineHandler() : null;
if (!string.IsNullOrEmpty(templateFileName))
{
var directory = Directory.Exists(configFile.ConfigFileLocation) ? configFile.ConfigFileLocation : Path.GetDirectoryName(configFile.ConfigFileLocation);
var templateFullPath = Path.Combine(directory, templateFileName);
if (!File.Exists(templateFullPath))
{
throw new FileNotFoundException($"Serverless template file {templateFullPath} not found");
}
ProcessServerlessTemplate(configInfo, templateFullPath);
}
else if(!string.IsNullOrEmpty(functionHandler))
{
var info = new LambdaFunctionInfo
{
Handler = functionHandler
};
info.Name = !string.IsNullOrEmpty(configFile.FunctionName) ? configFile.FunctionName : null;
if (string.IsNullOrEmpty(info.Name))
{
info.Name = functionHandler;
}
if(configFile.EnvironmentVariables != null)
{
ParseKeyValueOption(configFile.EnvironmentVariables, info.EnvironmentVariables);
}
configInfo.FunctionInfos.Add(info);
}
configInfo.FunctionInfos.Sort((x, y ) => string.CompareOrdinal(x.Name, y.Name));
return configInfo;
}
public static void ParseKeyValueOption(string keyValueString, IDictionary values)
{
if (string.IsNullOrWhiteSpace(keyValueString))
return;
try
{
var currentPos = 0;
while (currentPos != -1 && currentPos < keyValueString.Length)
{
string name;
GetNextToken(keyValueString, '=', ref currentPos, out name);
string value;
GetNextToken(keyValueString, ';', ref currentPos, out value);
if (string.IsNullOrEmpty(name))
throw new CommandLineParseException($"Error parsing option ({keyValueString}), format should be =;=");
values[name] = value ?? string.Empty;
}
}
catch (CommandLineParseException)
{
throw;
}
catch (Exception e)
{
throw new CommandLineParseException($"Error parsing option ({keyValueString}), format should be =;=: {e.Message}");
}
return;
}
private static void GetNextToken(string option, char endToken, ref int currentPos, out string token)
{
if (option.Length <= currentPos)
{
token = string.Empty;
return;
}
int tokenStart = currentPos;
int tokenEnd = -1;
bool inQuote = false;
if (option[currentPos] == '"')
{
inQuote = true;
tokenStart++;
currentPos++;
while (currentPos < option.Length && option[currentPos] != '"')
{
currentPos++;
}
if (option[currentPos] == '"')
tokenEnd = currentPos;
}
while (currentPos < option.Length && option[currentPos] != endToken)
{
currentPos++;
}
if (!inQuote)
{
if (currentPos < option.Length && option[currentPos] == endToken)
tokenEnd = currentPos;
}
if (tokenEnd == -1)
token = option.Substring(tokenStart);
else
token = option.Substring(tokenStart, tokenEnd - tokenStart);
currentPos++;
}
private static void ProcessServerlessTemplate(LambdaConfigInfo configInfo, string templateFilePath)
{
var content = File.ReadAllText(templateFilePath).Trim();
if(content[0] != '{')
{
ProcessYamlServerlessTemplate(configInfo, content);
}
else
{
ProcessJsonServerlessTemplate(configInfo, content);
}
}
private static void ProcessYamlServerlessTemplate(LambdaConfigInfo configInfo, string content)
{
var yaml = new YamlStream();
yaml.Load(new StringReader(content));
var root = (YamlMappingNode)yaml.Documents[0].RootNode;
if (root == null)
return;
YamlMappingNode resources = null;
if (root.Children.ContainsKey("Resources"))
{
resources = root.Children["Resources"] as YamlMappingNode;
ProcessYamlServerlessTemplateResourcesBased(configInfo, resources);
} else if (root.Children.ContainsKey("functions"))
{
resources = (YamlMappingNode) root.Children["functions"];
ProcessYamlServerlessTemplateFunctionBased(configInfo, resources);
}
;
}
private static void ProcessYamlServerlessTemplateResourcesBased(LambdaConfigInfo configInfo, YamlMappingNode resources)
{
if (resources == null)
return;
foreach (var resource in resources.Children)
{
var resourceBody = (YamlMappingNode) resource.Value;
var type = resourceBody.Children.ContainsKey("Type")
? ((YamlScalarNode) resourceBody.Children["Type"])?.Value
: null;
if (!string.Equals("AWS::Serverless::Function", type, StringComparison.Ordinal) &&
!string.Equals("AWS::Lambda::Function", type, StringComparison.Ordinal))
{
continue;
}
var properties = resourceBody.Children.ContainsKey("Properties")
? resourceBody.Children["Properties"] as YamlMappingNode
: null;
if (properties == null)
{
continue;
}
string handler = null;
if(properties.Children.ContainsKey("Handler"))
{
handler = ((YamlScalarNode)properties.Children["Handler"])?.Value;
}
if (string.IsNullOrEmpty(handler) && properties.Children.ContainsKey("ImageConfig"))
{
var imageConfigNode = properties.Children["ImageConfig"] as YamlMappingNode;
if (imageConfigNode.Children.ContainsKey("Command"))
{
var imageCommandNode = imageConfigNode.Children["Command"] as YamlSequenceNode;
// Grab the first element assuming that is the function handler.
var en = imageCommandNode.GetEnumerator();
en.MoveNext();
handler = ((YamlScalarNode)en.Current)?.Value;
}
}
if (!string.IsNullOrEmpty(handler))
{
var functionInfo = new LambdaFunctionInfo
{
Name = resource.Key.ToString(),
Handler = handler
};
configInfo.FunctionInfos.Add(functionInfo);
}
}
}
private static void ProcessYamlServerlessTemplateFunctionBased(LambdaConfigInfo configInfo, YamlMappingNode resources)
{
if (resources == null)
return;
foreach (var resource in resources.Children)
{
var resourceBody = (YamlMappingNode) resource.Value;
var handler = resourceBody.Children.ContainsKey("handler")
? ((YamlScalarNode) resourceBody.Children["handler"])?.Value
: null;
if (handler == null) continue;
if (string.IsNullOrEmpty(handler)) continue;
var functionInfo = new LambdaFunctionInfo
{
Name = resource.Key.ToString(),
Handler = handler
};
if (resourceBody.Children.TryGetValue("Environment", out var environmentProperty) && environmentProperty is YamlMappingNode)
{
if (((YamlMappingNode)environmentProperty).Children.TryGetValue("Environment", out var variableProperty) && variableProperty is YamlMappingNode)
{
foreach(var kvp in ((YamlMappingNode)variableProperty).Children)
{
if(kvp.Key is YamlScalarNode keyNode && keyNode.Value != null &&
kvp.Value is YamlScalarNode valueNode && valueNode.Value != null)
{
functionInfo.EnvironmentVariables[keyNode.Value] = valueNode.Value;
}
}
}
}
configInfo.FunctionInfos.Add(functionInfo);
}
}
private static void ProcessJsonServerlessTemplate(LambdaConfigInfo configInfo, string content)
{
var rootData = JsonDocument.Parse(content);
JsonElement resourcesNode;
if (!rootData.RootElement.TryGetProperty("Resources", out resourcesNode))
return;
foreach (var resourceProperty in resourcesNode.EnumerateObject())
{
var resource = resourceProperty.Value;
JsonElement typeProperty;
if (!resource.TryGetProperty("Type", out typeProperty))
continue;
var type = typeProperty.GetString();
JsonElement propertiesProperty;
if (!resource.TryGetProperty("Properties", out propertiesProperty))
continue;
if (!string.Equals("AWS::Serverless::Function", type, StringComparison.Ordinal) &&
!string.Equals("AWS::Lambda::Function", type, StringComparison.Ordinal))
{
continue;
}
string handler = null;
if (propertiesProperty.TryGetProperty("Handler", out var handlerProperty))
{
handler = handlerProperty.GetString();
}
else if(propertiesProperty.TryGetProperty("ImageConfig", out var imageConfigProperty) &&
imageConfigProperty.TryGetProperty("Command", out var imageCommandProperty))
{
if(imageCommandProperty.GetArrayLength() > 0)
{
// Grab the first element assuming that is the function handler.
var en = imageCommandProperty.EnumerateArray();
en.MoveNext();
handler = en.Current.GetString();
}
}
if (!string.IsNullOrEmpty(handler))
{
var functionInfo = new LambdaFunctionInfo
{
Name = resourceProperty.Name,
Handler = handler
};
if(propertiesProperty.TryGetProperty("Environment", out var environmentProperty) &&
environmentProperty.TryGetProperty("Variables", out var variablesProperty))
{
foreach(var property in variablesProperty.EnumerateObject())
{
if(property.Value.ValueKind == JsonValueKind.String)
{
functionInfo.EnvironmentVariables[property.Name] = property.Value.GetString();
}
}
}
configInfo.FunctionInfos.Add(functionInfo);
}
}
}
}
}