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  }