github.com/mweagle/Sparta@v1.15.0/decorator/cloudmap.go (about) 1 package decorator 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/aws/aws-sdk-go/aws/session" 8 sparta "github.com/mweagle/Sparta" 9 spartaIAM "github.com/mweagle/Sparta/aws/iam/builder" 10 gocf "github.com/mweagle/go-cloudformation" 11 "github.com/pkg/errors" 12 "github.com/sirupsen/logrus" 13 ) 14 15 // NewCloudMapServiceDecorator returns an instance of CloudMapServiceDecorator 16 // which can be used to publish information into CloudMap 17 func NewCloudMapServiceDecorator(namespaceID gocf.Stringable, 18 serviceName gocf.Stringable) (*CloudMapServiceDecorator, error) { 19 if namespaceID == nil || 20 serviceName == nil { 21 return nil, 22 errors.Errorf("Both namespaceID and serviceName must not be nil for CloudMapServiceDecorator") 23 } 24 decorator := &CloudMapServiceDecorator{ 25 namespaceID: namespaceID, 26 serviceName: serviceName, 27 servicePublishers: make(map[string]interface{}), 28 nonce: fmt.Sprintf("%d", time.Now().Unix()), 29 } 30 31 return decorator, nil 32 } 33 34 // CloudMapServiceDecorator is an instance of a service decorator that 35 // publishes CloudMap info 36 type CloudMapServiceDecorator struct { 37 namespaceID gocf.Stringable 38 serviceName gocf.Stringable 39 Description gocf.Stringable 40 nonce string 41 // This is a list of decorators that handle the publishing of 42 // individual lambda and resources. That means we can't apply this until all the 43 // individual decorators are complete... 44 servicePublishers map[string]interface{} 45 } 46 47 // LogicalResourceName returns the CloudFormation Logical resource 48 // name that can be used to get information about the generated 49 // CloudFormation resource 50 func (cmsd *CloudMapServiceDecorator) LogicalResourceName() string { 51 jsonData, jsonDataErr := cmsd.serviceName.String().MarshalJSON() 52 if jsonDataErr != nil { 53 jsonData = []byte(fmt.Sprintf("%#v", cmsd.serviceName)) 54 } 55 return sparta.CloudFormationResourceName("CloudMap", string(jsonData)) 56 } 57 58 // DecorateService satisfies the ServiceDecoratorHookHandler interface 59 func (cmsd *CloudMapServiceDecorator) DecorateService(context map[string]interface{}, 60 serviceName string, 61 template *gocf.Template, 62 S3Bucket string, 63 S3Key string, 64 buildID string, 65 awsSession *session.Session, 66 noop bool, 67 logger *logrus.Logger) error { 68 69 // Create the service entry 70 serviceDiscoveryResourceName := cmsd.LogicalResourceName() 71 serviceDiscoveryResource := &gocf.ServiceDiscoveryService{ 72 NamespaceID: cmsd.namespaceID.String(), 73 Name: cmsd.serviceName.String(), 74 HealthCheckCustomConfig: &gocf.ServiceDiscoveryServiceHealthCheckCustomConfig{ 75 FailureThreshold: gocf.Integer(1), 76 }, 77 } 78 if cmsd.Description != nil { 79 serviceDiscoveryResource.Description = cmsd.Description.String() 80 } 81 template.AddResource(serviceDiscoveryResourceName, serviceDiscoveryResource) 82 83 // Then for each template decorator, apply it 84 for _, eachAttributeSet := range cmsd.servicePublishers { 85 resourceName := sparta.CloudFormationResourceName("CloudMapRes", fmt.Sprintf("%v", eachAttributeSet)) 86 resource := &gocf.ServiceDiscoveryInstance{ 87 InstanceAttributes: eachAttributeSet, 88 ServiceID: gocf.Ref(serviceDiscoveryResourceName).String(), 89 //InstanceID: gocf.String(eachLookupName), 90 } 91 template.AddResource(resourceName, resource) 92 } 93 return nil 94 } 95 96 func (cmsd *CloudMapServiceDecorator) publish(lookupName string, 97 resourceName string, 98 resource gocf.ResourceProperties, 99 userAttributes map[string]interface{}) error { 100 101 _, exists := cmsd.servicePublishers[lookupName] 102 if exists { 103 return errors.Errorf("CloudMap discovery info for lookup name `%s` is already defined. Instance names must e unique", lookupName) 104 } 105 106 attributes := make(map[string]interface{}) 107 attributes["Ref"] = gocf.Ref(resourceName) 108 attributes["Type"] = resource.CfnResourceType() 109 for _, eachAttribute := range resource.CfnResourceAttributes() { 110 attributes[eachAttribute] = gocf.GetAtt(resourceName, eachAttribute) 111 } 112 for eachKey, eachValue := range userAttributes { 113 attributes[eachKey] = eachValue 114 } 115 attributes["Name"] = lookupName 116 cmsd.servicePublishers[lookupName] = attributes 117 return nil 118 } 119 120 // PublishResource publishes the known outputs and attributes for the 121 // given ResourceProperties instance 122 func (cmsd *CloudMapServiceDecorator) PublishResource(lookupName string, 123 resourceName string, 124 resource gocf.ResourceProperties, 125 addditionalProperties map[string]interface{}) error { 126 127 return cmsd.publish(lookupName, 128 resourceName, 129 resource, 130 addditionalProperties) 131 } 132 133 //PublishLambda publishes the known outputs for the given sparta 134 //AWS Lambda function 135 func (cmsd *CloudMapServiceDecorator) PublishLambda(lookupName string, 136 lambdaInfo *sparta.LambdaAWSInfo, 137 additionalAttributes map[string]interface{}) error { 138 lambdaEntry := &gocf.LambdaFunction{} 139 140 return cmsd.publish(lookupName, 141 lambdaInfo.LogicalResourceName(), 142 lambdaEntry, 143 additionalAttributes) 144 } 145 146 // EnableDiscoverySupport enables the IAM privs for the CloudMap ServiceID 147 // created by this stack as well as any additional serviceIDs 148 func (cmsd *CloudMapServiceDecorator) EnableDiscoverySupport(lambdaInfo *sparta.LambdaAWSInfo, 149 additionalServiceIDs ...string) error { 150 151 // Update the environment 152 lambdaOptions := lambdaInfo.Options 153 if lambdaOptions == nil { 154 lambdaOptions = &sparta.LambdaFunctionOptions{} 155 } 156 lambdaEnvVars := lambdaOptions.Environment 157 if lambdaEnvVars == nil { 158 lambdaOptions.Environment = make(map[string]*gocf.StringExpr) 159 } 160 lambdaOptions.Environment[EnvVarCloudMapNamespaceID] = cmsd.namespaceID.String() 161 lambdaOptions.Environment[EnvVarCloudMapServiceID] = gocf.Ref(cmsd.LogicalResourceName()).String() 162 163 // Ref: https://docs.aws.amazon.com/IAM/latest/UserGuide/list_awscloudmap.html 164 // arn:aws:servicediscovery:<region>:<account-id>:<resource-type>/<resource_name>. 165 // arn:${Partition}:servicediscovery:${Region}:${Account}:service/${ServiceName} 166 // DiscoverInstance, GetInstance, ListInstances, GetService 167 privGlobal := spartaIAM.Allow( 168 "servicediscovery:DiscoverInstances", 169 "servicediscovery:GetNamespace", 170 "servicediscovery:ListInstances").ForResource(). 171 Literal("*"). 172 ToPrivilege() 173 lambdaInfo.RoleDefinition.Privileges = append(lambdaInfo.RoleDefinition.Privileges, privGlobal) 174 175 privScoped := spartaIAM.Allow("servicediscovery:GetService").ForResource(). 176 Literal("arn:aws:servicediscovery:"). 177 Region(":"). 178 AccountID(":"). 179 Literal("service/"). 180 Ref(cmsd.LogicalResourceName(), ""). 181 ToPrivilege() 182 183 lambdaInfo.RoleDefinition.Privileges = append(lambdaInfo.RoleDefinition.Privileges, 184 privScoped) 185 186 for _, eachServiceVal := range additionalServiceIDs { 187 privScoped := spartaIAM.Allow("servicediscovery:GetService").ForResource(). 188 Literal("arn:aws:servicediscovery:"). 189 Region(":"). 190 AccountID(":"). 191 Literal("service/"). 192 Literal(eachServiceVal). 193 ToPrivilege() 194 lambdaInfo.RoleDefinition.Privileges = append(lambdaInfo.RoleDefinition.Privileges, 195 privScoped) 196 } 197 return nil 198 }