Skip to content

Commit c313f67

Browse files
committed
feat: Add YAML support to modify serverless template
1 parent 2669160 commit c313f67

23 files changed

+1293
-953
lines changed

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/CloudFormationTemplateFinder.cs

Lines changed: 0 additions & 95 deletions
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace Amazon.Lambda.Annotations.SourceGenerator
2+
{
3+
/// <summary>
4+
/// Specifies the format for the CloudFormation template.
5+
/// </summary>
6+
public enum CloudFormationTemplateFormat
7+
{
8+
Json,
9+
Yaml
10+
}
11+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
using System;
2+
using System.IO;
3+
using System.Linq;
4+
using Amazon.Lambda.Annotations.SourceGenerator.FileIO;
5+
using Newtonsoft.Json;
6+
using Newtonsoft.Json.Linq;
7+
8+
namespace Amazon.Lambda.Annotations.SourceGenerator
9+
{
10+
/// <summary>
11+
/// This class contains utility methods to determine the .NET project's root directory and resolve the AWS serverless template file path.
12+
/// </summary>
13+
public class CloudFormationTemplateHandler
14+
{
15+
private const string DEFAULT_CONFIG_FILE_NAME = "aws-lambda-tools-defaults.json";
16+
private const string DEFAULT_SERVERLESS_TEMPLATE_NAME = "serverless.template";
17+
18+
private readonly IFileManager _fileManager;
19+
private readonly IDirectoryManager _directoryManager;
20+
21+
public CloudFormationTemplateHandler(IFileManager fileManager, IDirectoryManager directoryManager)
22+
{
23+
_fileManager = fileManager;
24+
_directoryManager = directoryManager;
25+
}
26+
27+
/// <summary>
28+
/// This method takes any file path in the customer's .NET project and resolves the project root directory.
29+
/// The root directory is the folder that contains the .csproj file.
30+
/// </summary>
31+
/// <returns>The .NET project root directory path</returns>
32+
public string DetermineProjectRootDirectory(string sourceFilePath)
33+
{
34+
if (!_fileManager.Exists(sourceFilePath))
35+
return string.Empty;
36+
37+
var directoryPath = _directoryManager.GetDirectoryName(sourceFilePath);
38+
while (!string.IsNullOrEmpty(directoryPath))
39+
{
40+
if (_directoryManager.GetFiles(directoryPath, "*.csproj").Length == 1)
41+
return directoryPath;
42+
directoryPath = _directoryManager.GetDirectoryName(directoryPath);
43+
}
44+
45+
return string.Empty;
46+
}
47+
48+
/// <summary>
49+
/// Determines the path to the AWS serverless template file.
50+
/// If the file does not exist then an empty file is created before returning the path.
51+
/// </summary>
52+
public string FindTemplate(string projectRootDirectory)
53+
{
54+
var templateAbsolutePath = DetermineTemplatePath(projectRootDirectory);
55+
56+
if (!_fileManager.Exists(templateAbsolutePath))
57+
_fileManager.Create(templateAbsolutePath).Close();
58+
59+
return templateAbsolutePath;
60+
}
61+
62+
/// <summary>
63+
/// Checks if the AWS serverless template file exists.
64+
/// </summary>
65+
public bool DoesTemplateExist(string projectRootDirectory)
66+
{
67+
var templateAbsolutePath = DetermineTemplatePath(projectRootDirectory);
68+
return _fileManager.Exists(templateAbsolutePath);
69+
}
70+
71+
/// <summary>
72+
/// Determines the file format of the AWS serverless template.
73+
/// If the template does not exist or if the template is empty, then by default <see cref="CloudFormationTemplateFormat.Json"/> is returned.
74+
/// </summary>
75+
public CloudFormationTemplateFormat DetermineTemplateFormat(string templatePath)
76+
{
77+
if (!_fileManager.Exists(templatePath))
78+
{
79+
return CloudFormationTemplateFormat.Json;
80+
}
81+
82+
var content = _fileManager.ReadAllText(templatePath);
83+
content = content.Trim();
84+
if (string.IsNullOrEmpty(content))
85+
{
86+
return CloudFormationTemplateFormat.Json;
87+
}
88+
89+
return content[0] == '{' ? CloudFormationTemplateFormat.Json : CloudFormationTemplateFormat.Yaml;
90+
}
91+
92+
/// <summary>
93+
/// This is a helper method to determine the path to the AWS serverless template file.
94+
/// It will first look for <see cref="DEFAULT_CONFIG_FILE_NAME"/> inside the project root directory and will try to resolve the template file path from the `template` property.
95+
/// If <see cref="DEFAULT_CONFIG_FILE_NAME"/> does not exist or if the 'template' property is not found, then default to projectRootDirectory/<see cref="DEFAULT_SERVERLESS_TEMPLATE_NAME"/>
96+
/// </summary>
97+
private string DetermineTemplatePath(string projectRootDirectory)
98+
{
99+
if (!_directoryManager.Exists(projectRootDirectory))
100+
throw new DirectoryNotFoundException("Failed to find the project root directory");
101+
102+
var templateAbsolutePath = string.Empty;
103+
104+
var defaultConfigFile = _directoryManager.GetFiles(projectRootDirectory, DEFAULT_CONFIG_FILE_NAME, SearchOption.AllDirectories)
105+
.FirstOrDefault();
106+
107+
if (_fileManager.Exists(defaultConfigFile))
108+
// the templateAbsolutePath will be empty if the template property is not found in the default config file
109+
templateAbsolutePath = GetTemplatePathFromDefaultConfigFile(defaultConfigFile);
110+
111+
// if the default config file does not exist or if the template property is not found in the default config file
112+
// set the template path inside the project root directory.
113+
if (string.IsNullOrEmpty(templateAbsolutePath))
114+
templateAbsolutePath = Path.Combine(projectRootDirectory, DEFAULT_SERVERLESS_TEMPLATE_NAME);
115+
116+
return templateAbsolutePath;
117+
}
118+
/// <summary>
119+
/// This method parses the default config file and tries to resolve the serverless template path from the 'template' property.
120+
/// </summary>
121+
private string GetTemplatePathFromDefaultConfigFile(string defaultConfigFile)
122+
{
123+
JToken rootToken;
124+
try
125+
{
126+
rootToken = JObject.Parse(_fileManager.ReadAllText(defaultConfigFile));
127+
}
128+
catch (Exception)
129+
{
130+
return string.Empty;
131+
}
132+
133+
var templateRelativePath = rootToken["template"]?.ToObject<string>();
134+
135+
if (string.IsNullOrEmpty(templateRelativePath))
136+
return string.Empty;
137+
138+
var templateAbsolutePath = Path.Combine(_directoryManager.GetDirectoryName(defaultConfigFile), templateRelativePath);
139+
return templateAbsolutePath;
140+
}
141+
}
142+
}

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Generator.cs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Collections.Immutable;
4-
using System.Diagnostics;
52
using System.Linq;
63
using System.Text;
74
using Amazon.Lambda.Annotations.SourceGenerator.Diagnostics;
@@ -11,9 +8,7 @@
118
using Amazon.Lambda.Annotations.SourceGenerator.Templates;
129
using Amazon.Lambda.Annotations.SourceGenerator.Writers;
1310
using Microsoft.CodeAnalysis;
14-
using Microsoft.CodeAnalysis.Diagnostics;
1511
using Microsoft.CodeAnalysis.Text;
16-
using Microsoft.CodeAnalysis.VisualBasic;
1712

1813
namespace Amazon.Lambda.Annotations.SourceGenerator
1914
{
@@ -22,7 +17,6 @@ public class Generator : ISourceGenerator
2217
{
2318
private readonly IFileManager _fileManager = new FileManager();
2419
private readonly IDirectoryManager _directoryManager = new DirectoryManager();
25-
private readonly ITemplateWriter _jsonWriter = new JsonWriter();
2620

2721
public Generator()
2822
{
@@ -69,7 +63,7 @@ public void Execute(GeneratorExecutionContext context)
6963

7064
var annotationReport = new AnnotationReport();
7165

72-
var templateFinder = new CloudFormationTemplateFinder(_fileManager, _directoryManager);
66+
var templateHandler = new CloudFormationTemplateHandler(_fileManager, _directoryManager);
7367

7468
foreach (var lambdaMethod in receiver.LambdaMethods)
7569
{
@@ -117,12 +111,22 @@ public void Execute(GeneratorExecutionContext context)
117111
// Run the CloudFormation sync if any LambdaMethods exists. Also run if no LambdaMethods exists but there is a
118112
// CloudFormation template in case orphaned functions in the template need to be removed.
119113
// Both checks are required because if there is no template but there are LambdaMethods the CF template the template will be created.
120-
if (receiver.LambdaMethods.Any() || templateFinder.DoesCloudFormationTemplateExist(receiver.ProjectDirectory))
114+
if (receiver.LambdaMethods.Any() || templateHandler.DoesTemplateExist(receiver.ProjectDirectory))
121115
{
122-
annotationReport.CloudFormationTemplatePath = templateFinder.FindCloudFormationTemplate(receiver.ProjectDirectory);
116+
annotationReport.CloudFormationTemplatePath = templateHandler.FindTemplate(receiver.ProjectDirectory);
123117
annotationReport.ProjectRootDirectory = receiver.ProjectDirectory;
124-
var cloudFormationJsonWriter = new CloudFormationJsonWriter(_fileManager, _directoryManager, _jsonWriter, diagnosticReporter);
125-
cloudFormationJsonWriter.ApplyReport(annotationReport);
118+
var templateFormat = templateHandler.DetermineTemplateFormat(annotationReport.CloudFormationTemplatePath);
119+
ITemplateWriter templateWriter;
120+
if (templateFormat == CloudFormationTemplateFormat.Json)
121+
{
122+
templateWriter = new JsonWriter();
123+
}
124+
else
125+
{
126+
templateWriter = new YamlWriter();
127+
}
128+
var cloudFormationWriter = new CloudFormationWriter(_fileManager, _directoryManager, templateWriter, diagnosticReporter);
129+
cloudFormationWriter.ApplyReport(annotationReport);
126130
}
127131

128132
}

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/AnnotationReport.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ public class AnnotationReport
66
{
77
public IList<ILambdaFunctionSerializable> LambdaFunctions { get; } = new List<ILambdaFunctionSerializable>();
88
public string CloudFormationTemplatePath{ get; set; }
9-
public string ProjectRootDirectory { get; set; }
9+
public string ProjectRootDirectory { get; set; }
1010
}
1111
}

Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/SyntaxReceiver.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
2828
{
2929
if(this.ProjectDirectory == null && context.Node is ClassDeclarationSyntax)
3030
{
31-
var templateFinder = new CloudFormationTemplateFinder(_fileManager, _directoryManager);
32-
this.ProjectDirectory = templateFinder.DetermineProjectRootDirectory(context.Node.SyntaxTree.FilePath);
31+
var templateHandler = new CloudFormationTemplateHandler(_fileManager, _directoryManager);
32+
this.ProjectDirectory = templateHandler.DetermineProjectRootDirectory(context.Node.SyntaxTree.FilePath);
3333
}
3434

3535
// any method with at least one attribute is a candidate of function generation

0 commit comments

Comments
 (0)