// NewCloudformation creates a new Cloudformation from the AWS API's cloudformation.Stack func NewCloudformation(region string, stack *cloudformation.Stack) *Cloudformation { a := Cloudformation{ Resource: Resource{ region: reapable.Region(region), id: reapable.ID(*stack.StackId), Name: *stack.StackName, Tags: make(map[string]string), reaperState: state.NewStateWithUntil(time.Now().Add(config.Notifications.FirstStateDuration.Duration)), }, Stack: *stack, } // because getting resources is rate limited... go func() { a.Lock() for resource := range cloudformationResources(a.Region().String(), a.ID().String()) { a.Resources = append(a.Resources, *resource) } a.Unlock() }() for _, tag := range stack.Tags { a.Resource.Tags[*tag.Key] = *tag.Value } if a.Tagged(reaperTag) { // restore previously tagged state a.reaperState = state.NewStateWithTag(a.Resource.Tag(reaperTag)) } else { // initial state a.reaperState = state.NewState() } return &a }
// NewAutoScalingGroup creates an AutoScalingGroup from the AWS API's autoscaling.Group func NewAutoScalingGroup(region string, asg *autoscaling.Group) *AutoScalingGroup { a := AutoScalingGroup{ Resource: Resource{ region: reapable.Region(region), id: reapable.ID(*asg.AutoScalingGroupName), Name: *asg.AutoScalingGroupName, Tags: make(map[string]string), }, Group: *asg, } for _, instance := range asg.Instances { a.Instances = append(a.Instances, reapable.ID(*instance.InstanceId)) } for _, tag := range asg.Tags { a.Resource.Tags[*tag.Key] = *tag.Value } if a.Tagged("aws:cloudformation:stack-name") { a.Dependency = true a.IsInCloudformation = true } if a.Tagged(reaperTag) { // restore previously tagged state a.reaperState = state.NewStateWithTag(a.Tag(reaperTag)) } else { // initial state a.reaperState = state.NewState() } return &a }
// NewSecurityGroup creates an SecurityGroup from the AWS API's ec2.SecurityGroup func NewSecurityGroup(region string, sg *ec2.SecurityGroup) *SecurityGroup { s := SecurityGroup{ Resource: Resource{ id: reapable.ID(*sg.GroupId), region: reapable.Region(region), Name: *sg.GroupName, Tags: make(map[string]string), }, SecurityGroup: *sg, } for _, tag := range sg.Tags { s.Resource.Tags[*tag.Key] = *tag.Value } if s.Tagged("aws:cloudformation:stack-name") { s.Dependency = true s.IsInCloudformation = true } if s.Tagged(reaperTag) { // restore previously tagged state s.reaperState = state.NewStateWithTag(s.Resource.Tag(reaperTag)) } else { // initial state s.reaperState = state.NewState() } return &s }
// AutoScalingGroupInstanceIDs returns a map of regions to a map of ids to bools // the bool value is whether the instance with that region/id is in an ASG func AutoScalingGroupInstanceIDs(a *AutoScalingGroup) map[reapable.Region]map[reapable.ID]bool { // maps region to id to bool inASG := make(map[reapable.Region]map[reapable.ID]bool) for _, region := range config.Regions { inASG[reapable.Region(region)] = make(map[reapable.ID]bool) } for _, instanceID := range a.Instances { // add the instance to the map inASG[a.Region()][instanceID] = true } return inASG }
// NewInstance creates an Instance from the AWS API's ec2.Instance func NewInstance(region string, instance *ec2.Instance) *Instance { a := Instance{ Resource: Resource{ id: reapable.ID(*instance.InstanceId), region: reapable.Region(region), // passed in cause not possible to extract out of api Tags: make(map[string]string), }, SecurityGroups: make(map[reapable.ID]string), Instance: *instance, } for _, sg := range instance.SecurityGroups { if sg != nil { a.SecurityGroups[reapable.ID(*sg.GroupId)] = *sg.GroupName } } for _, tag := range instance.Tags { a.Resource.Tags[*tag.Key] = *tag.Value } if a.Tagged("aws:cloudformation:stack-name") { a.Dependency = true a.IsInCloudformation = true } if a.Tagged("aws:autoscaling:groupName") { a.Dependency = true } if a.Tagged("aws:autoscaling:groupName") { a.AutoScaled = true } a.Name = a.Tag("Name") if a.Tagged(reaperTag) { // restore previously tagged state a.reaperState = state.NewStateWithTag(a.Tag(reaperTag)) } else { // initial state a.reaperState = state.NewState() } return &a }
// Filter is part of the filter.Filterable interface func (a *AutoScalingGroup) Filter(filter filters.Filter) bool { matched := false // map function names to function calls switch filter.Function { case "SizeGreaterThan": if i, err := filter.Int64Value(0); err == nil && a.sizeGreaterThan(i) { matched = true } case "SizeLessThan": if i, err := filter.Int64Value(0); err == nil && a.sizeLessThan(i) { matched = true } case "SizeEqualTo": if i, err := filter.Int64Value(0); err == nil && a.sizeEqualTo(i) { matched = true } case "SizeLessThanOrEqualTo": if i, err := filter.Int64Value(0); err == nil && a.sizeLessThanOrEqualTo(i) { matched = true } case "SizeGreaterThanOrEqualTo": if i, err := filter.Int64Value(0); err == nil && a.sizeGreaterThanOrEqualTo(i) { matched = true } case "CreatedTimeInTheLast": d, err := time.ParseDuration(filter.Arguments[0]) if err == nil && a.CreatedTime != nil && time.Since(*a.CreatedTime) < d { matched = true } case "CreatedTimeNotInTheLast": d, err := time.ParseDuration(filter.Arguments[0]) if err == nil && a.CreatedTime != nil && time.Since(*a.CreatedTime) > d { matched = true } case "InCloudformation": if b, err := filter.BoolValue(0); err == nil && a.IsInCloudformation == b { matched = true } case "Region": for _, region := range filter.Arguments { if a.Region() == reapable.Region(region) { matched = true } } case "NotRegion": // was this resource's region one of those in the NOT list regionSpecified := false for _, region := range filter.Arguments { if a.Region() == reapable.Region(region) { regionSpecified = true } } if !regionSpecified { matched = true } case "Tagged": if a.Tagged(filter.Arguments[0]) { matched = true } case "NotTagged": if !a.Tagged(filter.Arguments[0]) { matched = true } case "TagNotEqual": if a.Tag(filter.Arguments[0]) != filter.Arguments[1] { matched = true } case "ReaperState": if a.reaperState.State.String() == filter.Arguments[0] { matched = true } case "NotReaperState": if a.reaperState.State.String() != filter.Arguments[0] { matched = true } case "Named": if a.Name == filter.Arguments[0] { matched = true } case "NotNamed": if a.Name != filter.Arguments[0] { matched = true } case "IsDependency": if b, err := filter.BoolValue(0); err == nil && a.Dependency == b { matched = true } case "NameContains": if strings.Contains(a.Name, filter.Arguments[0]) { matched = true } case "NotNameContains": if !strings.Contains(a.Name, filter.Arguments[0]) { matched = true } default: log.Error(fmt.Sprintf("No function %s could be found for filtering AutoScalingGroups.", filter.Function)) } return matched }
// Filter is part of the filter.Filterable interface func (a *SecurityGroup) Filter(filter filters.Filter) bool { matched := false // map function names to function calls switch filter.Function { case "InCloudformation": if b, err := filter.BoolValue(0); err == nil && a.IsInCloudformation == b { matched = true } case "Region": for _, region := range filter.Arguments { if a.Region() == reapable.Region(region) { matched = true } } case "NotRegion": // was this resource's region one of those in the NOT list regionSpecified := false for _, region := range filter.Arguments { if a.Region() == reapable.Region(region) { regionSpecified = true } } if !regionSpecified { matched = true } case "Tagged": if a.Tagged(filter.Arguments[0]) { matched = true } case "NotTagged": if !a.Tagged(filter.Arguments[0]) { matched = true } case "TagNotEqual": if a.Tag(filter.Arguments[0]) != filter.Arguments[1] { matched = true } case "ReaperState": if a.reaperState.State.String() == filter.Arguments[0] { matched = true } case "NotReaperState": if a.reaperState.State.String() != filter.Arguments[0] { matched = true } case "Named": if a.Name == filter.Arguments[0] { matched = true } case "NotNamed": if a.Name != filter.Arguments[0] { matched = true } case "IsDependency": if b, err := filter.BoolValue(0); err == nil && a.Dependency == b { matched = true } case "NameContains": if strings.Contains(a.Name, filter.Arguments[0]) { matched = true } case "NotNameContains": if !strings.Contains(a.Name, filter.Arguments[0]) { matched = true } default: log.Error(fmt.Sprintf("No function %s could be found for filtering SecurityGroups.", filter.Function)) } return matched }
func processToken(h *HTTPApi) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, req *http.Request) { if err := req.ParseForm(); err != nil { writeResponse(w, http.StatusBadRequest, "Bad query string") return } userToken := req.Form.Get(h.conf.Token) if userToken == "" { writeResponse(w, http.StatusBadRequest, "Token Missing") return } if u, err := url.QueryUnescape(userToken); err == nil { userToken = u } else { writeResponse(w, http.StatusBadRequest, "Invalid Token, could not decode data") return } job, err := token.Untokenize(h.conf.TokenSecret, userToken) if err != nil { writeResponse(w, http.StatusBadRequest, "Invalid Token, Could not untokenize") return } if job.Expired() == true { writeResponse(w, http.StatusBadRequest, "Token expired") return } // find reapable associated with the job r, err := reapables.Get(reapable.Region(job.Region), reapable.ID(job.ID)) if err != nil { writeResponse(w, http.StatusInternalServerError, err.Error()) return } switch job.Action { case token.J_DELAY: log.Debug("Delay request received for %s in region %s until %s", job.ID, job.Region, job.IgnoreUntil.String()) s := r.ReaperState() ok, err := r.Save(state.NewStateWithUntilAndState(s.Until.Add(job.IgnoreUntil), s.State)) if err != nil { writeResponse(w, http.StatusInternalServerError, err.Error()) return } if !ok { writeResponse(w, http.StatusInternalServerError, fmt.Sprintf("Delay failed for %s.", r.ReapableDescriptionTiny())) return } reaperevents.NewEvent("Reaper: Delay Request Received", fmt.Sprintf("Delay for %s in region %s until %s", job.ID, job.Region, job.IgnoreUntil.String()), nil, []string{}, ) reaperevents.NewCountStatistic("reaper.reapables.requests", []string{"type:delay"}) case token.J_TERMINATE: log.Debug("Terminate request received for %s in region %s.", job.ID, job.Region) ok, err := r.Terminate() if err != nil { writeResponse(w, http.StatusInternalServerError, err.Error()) return } if !ok { writeResponse(w, http.StatusInternalServerError, fmt.Sprintf("Terminate failed for %s.", r.ReapableDescriptionTiny())) return } reaperevents.NewEvent("Reaper: Terminate Request Received", r.ReapableDescriptionShort(), nil, []string{}) reaperevents.NewCountStatistic("reaper.reapables.requests", []string{"type:terminate"}) case token.J_WHITELIST: log.Debug("Whitelist request received for %s in region %s", job.ID, job.Region) ok, err := r.Whitelist() if err != nil { writeResponse(w, http.StatusInternalServerError, err.Error()) return } if !ok { writeResponse(w, http.StatusInternalServerError, fmt.Sprintf("Whitelist failed for %s.", r.ReapableDescriptionTiny())) return } reaperevents.NewEvent("Reaper: Whitelist Request Received", r.ReapableDescriptionShort(), nil, []string{}) reaperevents.NewCountStatistic("reaper.reapables.requests", []string{"type:whitelist"}) case token.J_STOP: log.Debug("Stop request received for %s in region %s", job.ID, job.Region) ok, err := r.Stop() if err != nil { writeResponse(w, http.StatusInternalServerError, err.Error()) return } if !ok { writeResponse(w, http.StatusInternalServerError, fmt.Sprintf("Stop failed for %s.", r.ReapableDescriptionTiny())) return } reaperevents.NewEvent("Reaper: Stop Request Received", r.ReapableDescriptionShort(), nil, []string{}) reaperevents.NewCountStatistic("reaper.reapables.requests", []string{"type:stop"}) default: log.Error("Unrecognized job token received.") writeResponse(w, http.StatusInternalServerError, "Unrecognized job token.") return } var consoleURL *url.URL switch t := r.(type) { case *reaperaws.Instance: consoleURL = t.AWSConsoleURL() case *reaperaws.AutoScalingGroup: consoleURL = t.AWSConsoleURL() default: log.Error("No AWSConsoleURL") } writeResponse(w, http.StatusOK, fmt.Sprintf("Success. Check %s out on the <a href=\"%s\">AWS Console.</a>", r.ReapableDescriptionTiny(), consoleURL)) } }
// Filter is part of the filter.Filterable interface func (a *Cloudformation) Filter(filter filters.Filter) bool { matched := false // map function names to function calls switch filter.Function { case "Status": if a.StackStatus != nil && *a.StackStatus == filter.Arguments[0] { // one of: // CREATE_COMPLETE // CREATE_IN_PROGRESS // CREATE_FAILED // DELETE_COMPLETE // DELETE_FAILED // DELETE_IN_PROGRESS // ROLLBACK_COMPLETE // ROLLBACK_FAILED // ROLLBACK_IN_PROGRESS // UPDATE_COMPLETE // UPDATE_COMPLETE_CLEANUP_IN_PROGRESS // UPDATE_IN_PROGRESS // UPDATE_ROLLBACK_COMPLETE // UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS // UPDATE_ROLLBACK_FAILED // UPDATE_ROLLBACK_IN_PROGRESS matched = true } case "NotStatus": if a.StackStatus != nil && *a.StackStatus != filter.Arguments[0] { matched = true } case "CreatedTimeInTheLast": d, err := time.ParseDuration(filter.Arguments[0]) if err == nil && a.CreationTime != nil && time.Since(*a.CreationTime) < d { matched = true } case "CreatedTimeNotInTheLast": d, err := time.ParseDuration(filter.Arguments[0]) if err == nil && a.CreationTime != nil && time.Since(*a.CreationTime) > d { matched = true } case "Region": for _, region := range filter.Arguments { if a.Region() == reapable.Region(region) { matched = true } } case "NotRegion": // was this resource's region one of those in the NOT list regionSpecified := false for _, region := range filter.Arguments { if a.Region() == reapable.Region(region) { regionSpecified = true } } if !regionSpecified { matched = true } case "Tagged": if a.Tagged(filter.Arguments[0]) { matched = true } case "NotTagged": if !a.Tagged(filter.Arguments[0]) { matched = true } case "TagNotEqual": if a.Tag(filter.Arguments[0]) != filter.Arguments[1] { matched = true } case "ReaperState": if a.reaperState.State.String() == filter.Arguments[0] { matched = true } case "NotReaperState": if a.reaperState.State.String() != filter.Arguments[0] { matched = true } case "Named": if a.Name == filter.Arguments[0] { matched = true } case "NotNamed": if a.Name != filter.Arguments[0] { matched = true } case "IsDependency": if b, err := filter.BoolValue(0); err == nil && a.Dependency == b { matched = true } case "NameContains": if strings.Contains(a.Name, filter.Arguments[0]) { matched = true } case "NotNameContains": if !strings.Contains(a.Name, filter.Arguments[0]) { matched = true } default: log.Error(fmt.Sprintf("No function %s could be found for filtering Cloudformations.", filter.Function)) } return matched }
// makes a slice of all filterables by appending // output of each filterable types aggregator function func allReapables() []reaperevents.Reapable { var resources []reaperevents.Reapable // initialize dependency and isInCloudformation dependency := make(map[reapable.Region]map[reapable.ID]bool) for _, region := range config.AWS.Regions { dependency[reapable.Region(region)] = make(map[reapable.ID]bool) } isInCloudformation := make(map[reapable.Region]map[reapable.ID]bool) for _, region := range config.AWS.Regions { isInCloudformation[reapable.Region(region)] = make(map[reapable.ID]bool) } // initialize the map of instances in ASGs instancesInASGs := make(map[reapable.Region]map[reapable.ID]bool) for _, region := range config.AWS.Regions { instancesInASGs[reapable.Region(region)] = make(map[reapable.ID]bool) } // without getCloudformations cannot populate basic dependency logic for c := range getCloudformations() { // because getting resources is rate limited... c.RLock() for _, resource := range c.Resources { if resource.PhysicalResourceId != nil { dependency[c.Region()][reapable.ID(*resource.PhysicalResourceId)] = true isInCloudformation[c.Region()][reapable.ID(*resource.PhysicalResourceId)] = true } } c.RUnlock() if config.Cloudformations.Enabled { resources = append(resources, c) } } for a := range getAutoScalingGroups() { // ASGs can be identified by name... if isInCloudformation[a.Region()][a.ID()] || isInCloudformation[a.Region()][reapable.ID(a.Name)] { a.IsInCloudformation = true } if dependency[a.Region()][a.ID()] || dependency[a.Region()][reapable.ID(a.Name)] { a.Dependency = true } // identify instances in an ASG instanceIDsInASGs := reaperaws.AutoScalingGroupInstanceIDs(a) for region := range instanceIDsInASGs { for instanceID := range instanceIDsInASGs[region] { instancesInASGs[region][instanceID] = true dependency[region][instanceID] = true } } if config.AutoScalingGroups.Enabled { resources = append(resources, a) } } // get all instances for i := range getInstances() { // add security groups to map of in use for id, name := range i.SecurityGroups { dependency[i.Region()][reapable.ID(name)] = true dependency[i.Region()][id] = true } if dependency[i.Region()][i.ID()] { i.Dependency = true } if isInCloudformation[i.Region()][i.ID()] { i.IsInCloudformation = true } if instancesInASGs[i.Region()][i.ID()] { i.AutoScaled = true } if config.Instances.Enabled { resources = append(resources, i) } } // get all security groups for s := range getSecurityGroups() { // if the security group is in use, it isn't reapable // names and IDs are used interchangeably by different parts of the API if isInCloudformation[s.Region()][s.ID()] { s.IsInCloudformation = true } if dependency[s.Region()][s.ID()] || dependency[s.Region()][reapable.ID(*s.GroupName)] { s.Dependency = true } if config.SecurityGroups.Enabled { resources = append(resources, s) } } // get all the volumes for v := range getVolumes() { // if the volume is in use, it isn't reapable // names and IDs are used interchangeably by different parts of the API // sort of doesn't make sense for volume if isInCloudformation[v.Region()][v.ID()] { v.IsInCloudformation = true } // if it is a dependency or is attached to an instance if dependency[v.Region()][v.ID()] || len(v.AttachedInstanceIDs) > 0 { v.Dependency = true } if config.Volumes.Enabled { resources = append(resources, v) } } return resources }
func (a *Instance) Filter(filter filters.Filter) bool { matched := false // map function names to function calls switch filter.Function { case "State": if a.State != nil && *a.State.Name == filter.Arguments[0] { matched = true } case "InstanceType": if a.InstanceType != nil && *a.InstanceType == filter.Arguments[0] { matched = true } case "HasPublicIpAddress": if b, err := filter.BoolValue(0); err == nil && b == (a.PublicIpAddress != nil) { matched = true } case "PublicIpAddress": if a.PublicIpAddress != nil && *a.PublicIpAddress == filter.Arguments[0] { matched = true } case "InCloudformation": if b, err := filter.BoolValue(0); err == nil && a.IsInCloudformation == b { matched = true } case "AutoScaled": if b, err := filter.BoolValue(0); err == nil && a.AutoScaled == b { matched = true } // uses RFC3339 format // https://www.ietf.org/rfc/rfc3339.txt case "LaunchTimeBefore": t, err := time.Parse(time.RFC3339, filter.Arguments[0]) if err == nil && a.LaunchTime != nil && t.After(*a.LaunchTime) { matched = true } case "LaunchTimeAfter": t, err := time.Parse(time.RFC3339, filter.Arguments[0]) if err == nil && a.LaunchTime != nil && t.Before(*a.LaunchTime) { matched = true } case "LaunchTimeInTheLast": d, err := time.ParseDuration(filter.Arguments[0]) if err == nil && a.LaunchTime != nil && time.Since(*a.LaunchTime) < d { matched = true } case "LaunchTimeNotInTheLast": d, err := time.ParseDuration(filter.Arguments[0]) if err == nil && a.LaunchTime != nil && time.Since(*a.LaunchTime) > d { matched = true } case "Region": for _, region := range filter.Arguments { if a.Region() == reapable.Region(region) { matched = true } } case "NotRegion": // was this resource's region one of those in the NOT list regionSpecified := false for _, region := range filter.Arguments { if a.Region() == reapable.Region(region) { regionSpecified = true } } if !regionSpecified { matched = true } case "Tagged": if a.Tagged(filter.Arguments[0]) { matched = true } case "NotTagged": if !a.Tagged(filter.Arguments[0]) { matched = true } case "TagNotEqual": if a.Tag(filter.Arguments[0]) != filter.Arguments[1] { matched = true } case "ReaperState": if a.reaperState.State.String() == filter.Arguments[0] { matched = true } case "NotReaperState": if a.reaperState.State.String() != filter.Arguments[0] { matched = true } case "Named": if a.Name == filter.Arguments[0] { matched = true } case "NotNamed": if a.Name != filter.Arguments[0] { matched = true } case "IsDependency": if b, err := filter.BoolValue(0); err == nil && a.Dependency == b { matched = true } case "NameContains": if strings.Contains(a.Name, filter.Arguments[0]) { matched = true } case "NotNameContains": if !strings.Contains(a.Name, filter.Arguments[0]) { matched = true } default: log.Error(fmt.Sprintf("No function %s could be found for filtering Instances.", filter.Function)) } return matched }