github.com/mweagle/Sparta@v1.15.0/cloudformation_resources.go (about) 1 package sparta 2 3 import ( 4 "bytes" 5 "fmt" 6 "strings" 7 "text/template" 8 9 gocf "github.com/mweagle/go-cloudformation" 10 "github.com/sirupsen/logrus" 11 ) 12 13 // Utility function to marshal an interface 14 func marshalInterface(item interface{}) interface{} { 15 if item != nil { 16 return item 17 } 18 return nil 19 } 20 21 // Utility function to marshal an int 22 func marshalInt(intVal int64) *gocf.IntegerExpr { 23 if intVal != 0 { 24 return gocf.Integer(intVal) 25 } 26 return nil 27 } 28 29 // Utility function to marshal a string 30 func marshalString(stringVal string) *gocf.StringExpr { 31 if stringVal != "" { 32 return gocf.String(stringVal) 33 } 34 return nil 35 } 36 37 func marshalStringExpr(stringExpr gocf.Stringable) *gocf.StringExpr { 38 if stringExpr != nil { 39 return stringExpr.String() 40 } 41 return nil 42 } 43 44 // Utility function to marshal a string lsit 45 func marshalStringList(stringVals []string) *gocf.StringListExpr { 46 if len(stringVals) != 0 { 47 stringableList := make([]gocf.Stringable, len(stringVals)) 48 for eachIndex, eachStringVal := range stringVals { 49 stringableList[eachIndex] = gocf.String(eachStringVal) 50 } 51 return gocf.StringList(stringableList...) 52 } 53 return nil 54 } 55 56 // Utility function to marshal a boolean 57 func marshalBool(boolValue bool) *gocf.BoolExpr { 58 if !boolValue { 59 return gocf.Bool(boolValue) 60 } 61 return nil 62 } 63 64 // resourceOutputs is responsible for returning the conditional 65 // set of CloudFormation outputs for a given resource type. 66 func resourceOutputs(resourceName string, 67 resource gocf.ResourceProperties, 68 logger *logrus.Logger) ([]string, error) { 69 70 outputProps := []string{} 71 switch typedResource := resource.(type) { 72 case gocf.IAMRole: 73 // NOP 74 case *gocf.DynamoDBTable: 75 if typedResource.StreamSpecification != nil { 76 outputProps = append(outputProps, "StreamArn") 77 } 78 case gocf.DynamoDBTable: 79 if typedResource.StreamSpecification != nil { 80 outputProps = append(outputProps, "StreamArn") 81 } 82 case gocf.KinesisStream, 83 *gocf.KinesisStream: 84 outputProps = append(outputProps, "Arn") 85 case gocf.Route53RecordSet, 86 *gocf.Route53RecordSet: 87 // NOP 88 case gocf.S3Bucket, 89 *gocf.S3Bucket: 90 outputProps = append(outputProps, "DomainName", "WebsiteURL") 91 case gocf.SNSTopic, 92 *gocf.SNSTopic: 93 outputProps = append(outputProps, "TopicName") 94 case gocf.SQSQueue, 95 *gocf.SQSQueue: 96 outputProps = append(outputProps, "Arn", "QueueName") 97 default: 98 logger.WithFields(logrus.Fields{ 99 "ResourceType": fmt.Sprintf("%T", typedResource), 100 }).Warn("Discovery information for dependency not yet implemented") 101 } 102 return outputProps, nil 103 } 104 105 func newCloudFormationResource(resourceType string, logger *logrus.Logger) (gocf.ResourceProperties, error) { 106 resProps := gocf.NewResourceByType(resourceType) 107 if nil == resProps { 108 logger.WithFields(logrus.Fields{ 109 "Type": resourceType, 110 }).Fatal("Failed to create CloudFormation CustomResource!") 111 return nil, fmt.Errorf("unsupported CustomResourceType: %s", resourceType) 112 } 113 return resProps, nil 114 } 115 116 type discoveryDataTemplate struct { 117 ResourceID string 118 ResourceType string 119 ResourceProperties string 120 } 121 122 var discoveryDataForResourceDependency = ` 123 { 124 "ResourceID" : "<< .ResourceID >>", 125 "ResourceRef" : "{"Ref":"<< .ResourceID >>"}", 126 "ResourceType" : "<< .ResourceType >>", 127 "Properties" : { 128 << .ResourceProperties >> 129 } 130 } 131 ` 132 133 func discoveryResourceInfoForDependency(cfTemplate *gocf.Template, 134 logicalResourceName string, 135 logger *logrus.Logger) ([]byte, error) { 136 137 item, ok := cfTemplate.Resources[logicalResourceName] 138 if !ok { 139 return nil, nil 140 } 141 resourceOutputs, resourceOutputsErr := resourceOutputs(logicalResourceName, 142 item.Properties, 143 logger) 144 if resourceOutputsErr != nil { 145 return nil, resourceOutputsErr 146 } 147 // Template data 148 templateData := &discoveryDataTemplate{ 149 ResourceID: logicalResourceName, 150 ResourceType: item.Properties.CfnResourceType(), 151 } 152 quotedAttrs := make([]string, len(resourceOutputs)) 153 for eachIndex, eachOutput := range resourceOutputs { 154 quotedAttrs[eachIndex] = fmt.Sprintf(`"%s" :"{ "Fn::GetAtt" : [ "%s", "%s" ] }"`, 155 eachOutput, 156 logicalResourceName, 157 eachOutput) 158 } 159 templateData.ResourceProperties = strings.Join(quotedAttrs, ",") 160 161 // Create the data that can be stuffed into Environment 162 discoveryTemplate, discoveryTemplateErr := template.New("discoveryResourceData"). 163 Delims("<<", ">>"). 164 Parse(discoveryDataForResourceDependency) 165 if nil != discoveryTemplateErr { 166 return nil, discoveryTemplateErr 167 } 168 169 var templateResults bytes.Buffer 170 evalResultErr := discoveryTemplate.Execute(&templateResults, templateData) 171 return templateResults.Bytes(), evalResultErr 172 } 173 func safeAppendDependency(resource *gocf.Resource, dependencyName string) { 174 if nil == resource.DependsOn { 175 resource.DependsOn = []string{} 176 } 177 resource.DependsOn = append(resource.DependsOn, dependencyName) 178 } 179 func safeMetadataInsert(resource *gocf.Resource, key string, value interface{}) { 180 if nil == resource.Metadata { 181 resource.Metadata = make(map[string]interface{}) 182 } 183 resource.Metadata[key] = value 184 }