/// <summary> /// Renders the specified GetAtt intrinsic. /// </summary> /// <param name="getAttIntrinsic">The GetAtt intrinsic.</param> /// <param name="template">The template.</param> /// <param name="resource">The resource.</param> /// <returns>An <see cref="IndirectReference"/> to an attribute on another resource.</returns> private static Reference Render(GetAttIntrinsic getAttIntrinsic, ITemplate template, ResourceMapping resource) { string attributeName; if (getAttIntrinsic.AttributeName is IIntrinsic) { if (!(getAttIntrinsic.AttributeName is RefIntrinsic refIntrinsic)) { // Only !Ref allowed here return(null); } attributeName = refIntrinsic.Evaluate(template).ToString(); } else { attributeName = getAttIntrinsic.AttributeName.ToString(); } // This GetAtt refers to a nested stack which may or may not have been imported as a terraform module. if (attributeName.StartsWith(TerraformExporterConstants.StackOutputQualifier)) { var referencedOutput = attributeName.Substring(TerraformExporterConstants.StackOutputQualifier.Length); if (resource.Module?.LogicalId == getAttIntrinsic.LogicalId && resource.Module.Outputs.Any(o => o.OutputKey == referencedOutput)) { // This reference is to a module output return(new ModuleReference($"{resource.Module.Name}.{referencedOutput}")); } // The reference is to an aws_cloudformation_stack as the use did not elect to import nested stacks. // Just lowercase "Outputs." which will then resolve to the terraform resource. attributeName = attributeName.Replace( TerraformExporterConstants.StackOutputQualifier, TerraformExporterConstants.StackOutputQualifier.ToLowerInvariant()); } else { var traits = AwsSchema.GetResourceTraits(resource.TerraformType); attributeName = traits.AttributeMap.ContainsKey(attributeName) ? traits.AttributeMap[attributeName] : attributeName.CamelCaseToSnakeCase(); } if (getAttIntrinsic.ExtraData is IntrinsicInfo intrinsicInfo) { resource = intrinsicInfo.TargetResource; } return(template.Resources.Any(r => r.Name == resource.LogicalId) ? new IndirectReference($"{resource.Address}.{attributeName}") : null); }
/// <summary> /// Gets the resolved name of the target attribute. /// </summary> /// <param name="self">Instance of intrinsic.</param> /// <param name="template">The template.</param> /// <returns>Resolved name of attribute targeted by this intrinsic.</returns> public static string GetResolvedAttributeName(this GetAttIntrinsic self, ITemplate template) { if (self == null) { throw new ArgumentNullException(nameof(self)); } return(self.AttributeName is IIntrinsic intrinsic ? intrinsic.Evaluate(template).ToString() : self.AttributeName.ToString()); }
/// <summary> /// Gets the value of the property on the target terraform resource referenced by this <c>!GetAtt</c>. /// </summary> /// <param name="self">Instance of intrinsic.</param> /// <param name="template">The template.</param> /// <param name="targetResource">The target resource.</param> /// <returns>An <see cref="IGetAttTargetEvaluation"/> containing result of the underlying resource visit.</returns> public static IGetAttTargetEvaluation GetTargetValue(this GetAttIntrinsic self, ITemplate template, StateFileResourceInstance targetResource) { if (self == null) { throw new ArgumentNullException(nameof(self)); } var context = new TerraformAttributeGetterContext(self.GetResolvedAttributeName(template)); targetResource.Attributes.Accept(new TerraformAttributeGetterVisitor(), context); return(context); }
/// <summary> /// Begin processing an intrinsic. /// </summary> /// <param name="intrinsic">The intrinsic.</param> /// <param name="currentPath">The current path.</param> public void EnterIntrinsic(IIntrinsic intrinsic, PropertyPath currentPath) { if (this.lastWarning != null) { // Something that can't be resolved has been found, // so don't process any more here. return; } var clonedPath = currentPath.Clone(); try { if (this.parentIntrinsicPath == null) { // This is a "top level" intrinsic associated directly with a resource attribute. if (this.currentCloudFormationResource.Type == "AWS::Lambda::Permission" && currentPath.Path == "FunctionName" && intrinsic is RefIntrinsic) { // !! NASTY KLUDGE ALERT !! // AWS treats this property as a !Ref which is lambda function's name, but terraform actually wants the ARN here. // If I find more cases like this, then I'll put something into resource traits intrinsic = new GetAttIntrinsic( intrinsic.GetReferencedObjects(this.template).First(), "Arn"); } this.intrinsicInfos.Push(this.currentIntrinsicInfo); this.parentIntrinsicPath = clonedPath; this.currentIntrinsicInfo = this.GetIntrinsicInfo(intrinsic, currentPath); } else { // We have descended the graph to member intrinsic this.intrinsicInfos.Push(this.currentIntrinsicInfo); var intrinsicInfo = this.GetIntrinsicInfo(intrinsic, currentPath); this.currentIntrinsicInfo.NestedIntrinsics.Add(intrinsicInfo); this.currentIntrinsicInfo = intrinsicInfo; } } catch (DependencyResolutionWarning w) { this.lastWarning = w; } }
/// <summary> /// Creates an <see cref="IntrinsicInfo"/> for a <c>!GetAtt</c> intrinsic. /// </summary> /// <param name="getAttIntrinsic">The <c>!GetAtt</c> intrinsic.</param> /// <param name="currentPath">The current path.</param> /// <returns>An <see cref="IntrinsicInfo"/></returns> private IntrinsicInfo ProcessGetAtt(GetAttIntrinsic getAttIntrinsic, PropertyPath currentPath) { object evaluation; // Logical name of the resource being referenced by this !GetAtt var(referencedResourceName, attribute) = (Tuple <string, string>)getAttIntrinsic.Evaluate(this.template); // Is the reference to a nested stack module? var referencedModule = this.module.NestedModules.FirstOrDefault(m => m.LogicalId == referencedResourceName); if (referencedModule != null) { var targetModuleSummary = new ResourceMapping { AwsType = TerraformExporterConstants.AwsCloudFormationStack, LogicalId = referencedModule.LogicalId, PhysicalId = referencedModule.Name, Module = referencedModule }; var parts = attribute.Split('.'); evaluation = referencedModule.Outputs.Where(o => o.OutputKey == parts[1]) .Select(o => o.OutputValue).SingleOrDefault(); return(new IntrinsicInfo(currentPath, getAttIntrinsic, targetModuleSummary, evaluation)); } // State file instance of the resource being referenced by this !GetAtt var referencedResource = this.TerraformResources.FirstOrDefault(r => r.Name == referencedResourceName) ?.Instances.First(); if (referencedResource == null) { // If not found, then reference is to a resource that couldn't be imported eg. a custom resource. throw new UnsupportedResourceWarning( getAttIntrinsic, this.currentCloudFormationResource, currentPath); } // CloudFormation instance of the resource being referenced by this !GetAtt var cloudFormationResource = this.CloudFormationResources.First(r => r.LogicalResourceId == getAttIntrinsic.LogicalId); // Summary of the resource to which this !GetAtt refers to var targetResourceSummary = new ResourceMapping { AwsType = cloudFormationResource.ResourceType, LogicalId = cloudFormationResource.LogicalResourceId, PhysicalId = cloudFormationResource.PhysicalResourceId, TerraformType = this.TerraformResources.First( tr => tr.Name == cloudFormationResource.LogicalResourceId).Type }; // Now attempt to match up the CloudFormation resource attribute name with the corresponding terraform one // and get the current value from state. // First, look up the attribute map var traits = AwsSchema.GetResourceTraits(referencedResource.Parent.Type); if (traits.AttributeMap.ContainsKey(attribute)) { var token = referencedResource.Attributes[traits.AttributeMap[attribute]]; evaluation = GetEvaluation(token); } else if (attribute.StartsWith(TerraformExporterConstants.StackOutputQualifier)) { // Nested stack output reference var token = referencedResource.Attributes.SelectToken( attribute.Replace( TerraformExporterConstants.StackOutputQualifier, TerraformExporterConstants.StackOutputQualifier.ToLowerInvariant())); evaluation = GetEvaluation(token); } else { var result = getAttIntrinsic.GetTargetValue(this.template, referencedResource); if (result.Success) { evaluation = result.Value; } else { throw new UnreferenceableIntrinsicWarning( getAttIntrinsic, cloudFormationResource.TemplateResource, currentPath); } } return(new IntrinsicInfo(currentPath, getAttIntrinsic, targetResourceSummary, evaluation)); object GetEvaluation(JToken token) { if (token is JValue jv) { // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault switch (jv.Type) { case JTokenType.String: evaluation = jv.Value <string>(); break; case JTokenType.Integer: case JTokenType.Float: evaluation = jv.Value <double>(); break; case JTokenType.Boolean: evaluation = jv.Value <bool>(); break; default: throw new InvalidOperationException( $"Unexpected JValue type: {jv.Type} while processing {getAttIntrinsic}"); } } else { throw new InvalidOperationException( $"Unexpected JToken type: {token.Type} while processing {getAttIntrinsic}"); } return(evaluation); } }