using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
namespace Amazon.Lambda.Tools.TemplateProcessor
{
///
/// The updatable resource like a CloudFormation AWS::Lambda::Function. This class combines the UpdatableResourceDefinition
/// which identifies the fields that can be updated and IUpdatableResourceDataSource which abstracts the JSON or YAML definition.
///
public class UpdatableResource : IUpdatableResource
{
public string Name { get; }
public string ResourceType { get; }
public IList Fields { get; }
UpdatableResourceDefinition Definition { get; }
IUpdatableResourceDataSource DataSource { get; }
public UpdatableResource(string name, UpdatableResourceDefinition definition, IUpdatableResourceDataSource dataSource)
{
this.Name = name;
this.Definition = definition;
this.DataSource = dataSource;
this.Fields = new List();
foreach (var fieldDefinition in definition.Fields)
{
this.Fields.Add(new UpdatableResourceField(this, fieldDefinition, dataSource));
}
}
public string LambdaRuntime
{
get
{
var runtime = this.DataSource.GetValue("Runtime");
if(string.IsNullOrEmpty(runtime))
{
runtime = this.DataSource.GetValueFromRoot("Globals", "Function", "Runtime");
}
return runtime;
}
}
public string LambdaArchitecture
{
get
{
var architectures = this.DataSource.GetValueList("Architectures");
if(architectures == null || architectures.Length == 0)
{
architectures = this.DataSource.GetValueListFromRoot("Globals", "Function", "Architectures");
}
if(architectures == null || architectures.Length == 0)
{
return LambdaConstants.ARCHITECTURE_X86_64;
}
else if(architectures.Length == 1)
{
return architectures[0];
}
else
{
throw new LambdaToolsException("More then one architecture was specified. .NET Lambda functions only support a single architecture value for creating a deployment bundle for the specific architecture.", Common.DotNetCli.Tools.ToolsException.CommonErrorCode.InvalidParameterValue);
}
}
}
public string[] LambdaLayers
{
get
{
var layers = new List();
var resourceLayers = this.DataSource.GetValueList("Layers");
if (resourceLayers != null)
{
layers.AddRange(resourceLayers);
}
var globalLayers = this.DataSource.GetValueListFromRoot("Globals", "Function", "Layers");
if (globalLayers != null)
{
layers.AddRange(globalLayers);
}
return layers.Count == 0 ? null : layers.ToArray();
}
}
public CodeUploadType UploadType
{
get
{
var packageType = this.DataSource.GetValue("PackageType");
if (string.Equals("image", packageType, StringComparison.OrdinalIgnoreCase))
{
return CodeUploadType.Image;
}
return CodeUploadType.Zip;
}
}
public void SetEnvironmentVariable(string key, string value)
{
this.DataSource.SetValue(value, "Environment", "Variables", key);
}
public class UpdatableResourceField : IUpdateResourceField
{
public IUpdatableResource Resource => this._resource;
public UpdatableResource _resource;
private UpdatableResourceDefinition.FieldDefinition Field { get; }
private IUpdatableResourceDataSource DataSource;
public UpdatableResourceField(UpdatableResource resource, UpdatableResourceDefinition.FieldDefinition field, IUpdatableResourceDataSource dataSource)
{
this._resource = resource;
this.Field = field;
this.DataSource = dataSource;
}
public string Name => this.Field.Name;
public bool IsCode
{
get
{
if (!this.Field.IsCode)
{
return false;
}
if (!string.IsNullOrEmpty(this._resource.DataSource.GetValue("Code", "ZipFile")))
{
// The template contains embedded code.
return false;
}
else
{
string localPath = this._resource.DataSource.GetValueFromResource(LambdaConstants.CF_SERVERLESS_METADATA, LambdaConstants.CF_SERVERLESS_DOCKERCONTEXT);
// Pointing to already pushed image, no package needs to be done.
if (IsECRImage(localPath))
{
return false;
}
// Metadata points to local path that needs to be packaged up return true for code.
else if (!string.IsNullOrEmpty(localPath))
{
return true;
}
// If no Docker Metadata was set fallback to looking for a local path in the resource's ImageUri property.
localPath = this._resource.DataSource.GetValue(LambdaConstants.CF_LAMBDA_IMAGEURI);
// Pointing to already pushed image, no package needs to be done.
if (IsECRImage(localPath))
{
return false;
}
return true;
}
}
}
public bool IsImagePushed
{
get
{
string localPath = this._resource.DataSource.GetValueFromResource(LambdaConstants.CF_SERVERLESS_METADATA, LambdaConstants.CF_SERVERLESS_DOCKERCONTEXT);
// Pointing to already pushed image, no package needs to be done.
if (IsECRImage(localPath))
{
return true;
}
// Metadata points to local path that needs to be packaged up return true for code.
else if (!string.IsNullOrEmpty(localPath))
{
return false;
}
// If no Docker Metadata was set fallback to looking for a local path in the resource's ImageUri property.
localPath = this._resource.DataSource.GetValue(LambdaConstants.CF_LAMBDA_IMAGEURI);
// Pointing to already pushed image, no package needs to be done.
if (IsECRImage(localPath))
{
return true;
}
return false;
}
}
public string GetLocalPath()
{
return this.Field.GetLocalPath(this._resource.DataSource);
}
public void SetS3Location(string s3Bucket, string s3Key)
{
this.Field.SetS3Location(this._resource.DataSource, s3Bucket, s3Key);
}
public void SetImageUri(string imageUri)
{
this.Field.SetImageUri(this._resource.DataSource, imageUri);
}
public string GetMetadataDockerfile()
{
return this.DataSource.GetValueFromResource("Metadata", "Dockerfile");
}
public string GetMetadataDockerTag()
{
return this.DataSource.GetValueFromResource("Metadata", "DockerTag");
}
public Dictionary GetMetadataDockerBuildArgs()
{
return this.DataSource.GetValueDictionaryFromResource("Metadata", "DockerBuildArgs");
}
public static bool IsECRImage(string path)
{
return (!string.IsNullOrEmpty(path) && path.Contains("dkr.ecr") && Regex.Match(path.Split('.')[0], @"^\d{12}$").Success);
}
}
}
}