github.com/franciscocpg/up@v0.1.10/platform/lambda/stack/stack.go (about)

     1  package stack
     2  
     3  import (
     4  	"encoding/json"
     5  	"time"
     6  
     7  	"github.com/aws/aws-sdk-go/aws"
     8  	"github.com/aws/aws-sdk-go/aws/session"
     9  	"github.com/aws/aws-sdk-go/service/cloudformation"
    10  	"github.com/pkg/errors"
    11  
    12  	"github.com/apex/up"
    13  	"github.com/apex/up/internal/util"
    14  	"github.com/apex/up/platform/event"
    15  )
    16  
    17  // TODO: refactor New's hackiness
    18  
    19  // Stack represents a single CloudFormation stack.
    20  type Stack struct {
    21  	client *cloudformation.CloudFormation
    22  	events event.Events
    23  	config *up.Config
    24  }
    25  
    26  // New stack.
    27  func New(c *up.Config, events event.Events, region string) *Stack {
    28  	sess := session.New(aws.NewConfig().WithRegion(region))
    29  	return &Stack{
    30  		client: cloudformation.New(sess),
    31  		events: events,
    32  		config: c,
    33  	}
    34  }
    35  
    36  // Create the stack.
    37  func (s *Stack) Create(version string) error {
    38  	c := s.config
    39  	tmpl := template(c)
    40  	name := c.Name
    41  
    42  	b, err := json.MarshalIndent(tmpl, "", "  ")
    43  	if err != nil {
    44  		return errors.Wrap(err, "marshaling")
    45  	}
    46  
    47  	_, err = s.client.CreateStack(&cloudformation.CreateStackInput{
    48  		StackName:        &name,
    49  		TemplateBody:     aws.String(string(b)),
    50  		TimeoutInMinutes: aws.Int64(15),
    51  		DisableRollback:  aws.Bool(true),
    52  		Capabilities:     aws.StringSlice([]string{"CAPABILITY_NAMED_IAM"}),
    53  		Parameters: []*cloudformation.Parameter{
    54  			{
    55  				ParameterKey:   aws.String("Name"),
    56  				ParameterValue: &name,
    57  			},
    58  			{
    59  				ParameterKey:   aws.String("FunctionName"),
    60  				ParameterValue: &name,
    61  			},
    62  			{
    63  				ParameterKey:   aws.String("FunctionVersion"),
    64  				ParameterValue: &version,
    65  			},
    66  		},
    67  	})
    68  
    69  	if err != nil {
    70  		return errors.Wrap(err, "creating stack")
    71  	}
    72  
    73  	if err := s.report("create"); err != nil {
    74  		return errors.Wrap(err, "reporting events")
    75  	}
    76  
    77  	stack, err := s.getStack()
    78  	if err != nil {
    79  		return errors.Wrap(err, "fetching stack")
    80  	}
    81  
    82  	status := Status(*stack.StackStatus)
    83  	if status.State() == Failure {
    84  		return errors.New(*stack.StackStatusReason)
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  // Delete the stack, optionally waiting for completion.
    91  func (s *Stack) Delete(wait bool) error {
    92  	_, err := s.client.DeleteStack(&cloudformation.DeleteStackInput{
    93  		StackName: &s.config.Name,
    94  	})
    95  
    96  	if err != nil {
    97  		return errors.Wrap(err, "deleting")
    98  	}
    99  
   100  	if wait {
   101  		if err := s.report("delete"); err != nil {
   102  			return errors.Wrap(err, "reporting")
   103  		}
   104  	}
   105  
   106  	return nil
   107  }
   108  
   109  // Show resources.
   110  func (s *Stack) Show() error {
   111  	defer s.events.Time("platform.stack.show", nil)()
   112  
   113  	stack, err := s.getStack()
   114  	if err != nil {
   115  		return errors.Wrap(err, "fetching stack")
   116  	}
   117  
   118  	s.events.Emit("platform.stack.show.stack", event.Fields{
   119  		"stack": stack,
   120  	})
   121  
   122  	events, err := s.getLatestEvents()
   123  	if err != nil {
   124  		return errors.Wrap(err, "fetching latest events")
   125  	}
   126  
   127  	for _, e := range events {
   128  		s.events.Emit("platform.stack.show.event", event.Fields{
   129  			"event": e,
   130  		})
   131  	}
   132  
   133  	return nil
   134  }
   135  
   136  // report events.
   137  func (s *Stack) report(state string) error {
   138  	hit := make(map[string]bool)
   139  	tmpl := template(s.config)
   140  
   141  	defer s.events.Time("platform.stack."+state, event.Fields{
   142  		"resources": len(tmpl["Resources"].(Map)),
   143  	})()
   144  
   145  	for range time.Tick(time.Second) {
   146  		stack, err := s.getStack()
   147  
   148  		if util.IsNotFound(err) {
   149  			return nil
   150  		}
   151  
   152  		if err != nil {
   153  			return errors.Wrap(err, "fetching stack")
   154  		}
   155  
   156  		status := Status(*stack.StackStatus)
   157  
   158  		if status.IsDone() {
   159  			return nil
   160  		}
   161  
   162  		events, err := s.getEvents()
   163  
   164  		if util.IsNotFound(err) {
   165  			return nil
   166  		}
   167  
   168  		if err != nil {
   169  			return errors.Wrap(err, "fetching events")
   170  		}
   171  
   172  		for _, e := range events {
   173  			if hit[*e.EventId] {
   174  				continue
   175  			}
   176  			hit[*e.EventId] = true
   177  
   178  			s.events.Emit("platform.stack."+state+".event", event.Fields{
   179  				"event": e,
   180  			})
   181  		}
   182  	}
   183  
   184  	return nil
   185  }
   186  
   187  // getStack returns the stack.
   188  func (s *Stack) getStack() (*cloudformation.Stack, error) {
   189  	res, err := s.client.DescribeStacks(&cloudformation.DescribeStacksInput{
   190  		StackName: &s.config.Name,
   191  	})
   192  
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  
   197  	stack := res.Stacks[0]
   198  	return stack, nil
   199  }
   200  
   201  // getLatestEvents returns the latest events for each resource.
   202  func (s *Stack) getLatestEvents() (v []*cloudformation.StackEvent, err error) {
   203  	events, err := s.getEvents()
   204  	if err != nil {
   205  		return
   206  	}
   207  
   208  	hit := make(map[string]bool)
   209  
   210  	for _, e := range events {
   211  		id := *e.LogicalResourceId
   212  		if hit[id] {
   213  			continue
   214  		}
   215  
   216  		hit[id] = true
   217  		v = append(v, e)
   218  	}
   219  
   220  	return
   221  }
   222  
   223  // getEvents returns events.
   224  func (s *Stack) getEvents() (events []*cloudformation.StackEvent, err error) {
   225  	var next *string
   226  
   227  	for {
   228  		res, err := s.client.DescribeStackEvents(&cloudformation.DescribeStackEventsInput{
   229  			StackName: &s.config.Name,
   230  			NextToken: next,
   231  		})
   232  
   233  		if err != nil {
   234  			return nil, err
   235  		}
   236  
   237  		for _, e := range res.StackEvents {
   238  			events = append(events, e)
   239  		}
   240  
   241  		next = res.NextToken
   242  
   243  		if next == nil {
   244  			break
   245  		}
   246  	}
   247  
   248  	return
   249  }
   250  
   251  // getEventsByState returns events by state.
   252  func (s *Stack) getEventsByState(state State) (v []*cloudformation.StackEvent, err error) {
   253  	events, err := s.getEvents()
   254  	if err != nil {
   255  		return
   256  	}
   257  
   258  	for _, e := range events {
   259  		s := Status(*e.ResourceStatus)
   260  		if s.State() == state {
   261  			v = append(v, e)
   262  		}
   263  	}
   264  
   265  	return
   266  }