// 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 }
// 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 }
// 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 }
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)) } }
// 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 }