From 8349f6e77727543a07416a587b2872e413c0d374 Mon Sep 17 00:00:00 2001 From: Miles Crabill Date: Mon, 3 Aug 2015 11:24:16 -0700 Subject: [PATCH 1/4] fix initial state to avoid race condition --- aws/autoscaling.go | 4 +--- aws/awsresource.go | 10 +++++++--- aws/cloudformation.go | 4 +--- aws/instance.go | 4 +--- aws/securitygroup.go | 4 +--- reaper/reaper.go | 8 ++++---- state/state.go | 11 ++++------- state/stateenum_string.go | 4 ++-- 8 files changed, 21 insertions(+), 28 deletions(-) diff --git a/aws/autoscaling.go b/aws/autoscaling.go index e6c59d6..c4fe389 100644 --- a/aws/autoscaling.go +++ b/aws/autoscaling.go @@ -114,9 +114,7 @@ func NewAutoScalingGroup(region string, asg *autoscaling.Group) *AutoScalingGrou a.reaperState = state.NewStateWithTag(a.Tag(reaperTag)) } else { // initial state - a.reaperState = state.NewStateWithUntilAndState( - time.Now().Add(config.Notifications.FirstStateDuration.Duration), - state.FirstState) + a.reaperState = state.NewState() } return &a diff --git a/aws/awsresource.go b/aws/awsresource.go index 74512e3..6639936 100644 --- a/aws/awsresource.go +++ b/aws/awsresource.go @@ -52,6 +52,10 @@ func (a *AWSResource) SetReaperState(newState *state.State) { a.reaperState = newState } +func (a *AWSResource) SetUpdated(b bool) { + a.reaperState.Updated = b +} + // Owner extracts useful information out of the Owner tag which should // be parsable by mail.ParseAddress func (a *AWSResource) Owner() *mail.Address { @@ -83,7 +87,8 @@ func (a *AWSResource) IncrementState() bool { switch a.reaperState.State { default: - // shouldn't ever be hit, but if it is + fallthrough + case state.InitialState: // set state to the FirstState newState = state.FirstState until = until.Add(config.Notifications.FirstStateDuration.Duration) @@ -108,11 +113,10 @@ func (a *AWSResource) IncrementState() bool { if newState != a.reaperState.State { updated = true + a.reaperState = state.NewStateWithUntilAndState(until, newState) log.Notice("Updating state for %s. New state: %s.", a.ReapableDescriptionTiny(), newState.String()) } - a.reaperState = state.NewStateWithUntilAndState(until, newState) - return updated } diff --git a/aws/cloudformation.go b/aws/cloudformation.go index e14a966..7d6386e 100644 --- a/aws/cloudformation.go +++ b/aws/cloudformation.go @@ -62,9 +62,7 @@ func NewCloudformation(region string, stack *cloudformation.Stack) *Cloudformati a.reaperState = state.NewStateWithTag(a.AWSResource.Tag(reaperTag)) } else { // initial state - a.reaperState = state.NewStateWithUntilAndState( - time.Now().Add(config.Notifications.FirstStateDuration.Duration), - state.FirstState) + a.reaperState = state.NewState() } return &a diff --git a/aws/instance.go b/aws/instance.go index f484d03..5b2b2f4 100644 --- a/aws/instance.go +++ b/aws/instance.go @@ -92,9 +92,7 @@ func NewInstance(region string, instance *ec2.Instance) *Instance { i.reaperState = state.NewStateWithTag(i.Tag(reaperTag)) } else { // initial state - i.reaperState = state.NewStateWithUntilAndState( - time.Now().Add(config.Notifications.FirstStateDuration.Duration), - state.FirstState) + i.reaperState = state.NewState() } return &i diff --git a/aws/securitygroup.go b/aws/securitygroup.go index e7425f6..c39b2a0 100644 --- a/aws/securitygroup.go +++ b/aws/securitygroup.go @@ -43,9 +43,7 @@ func NewSecurityGroup(region string, sg *ec2.SecurityGroup) *SecurityGroup { s.reaperState = state.NewStateWithTag(s.AWSResource.Tag(reaperTag)) } else { // initial state - s.reaperState = state.NewStateWithUntilAndState( - time.Now().Add(config.Notifications.FirstStateDuration.Duration), - state.FirstState) + s.reaperState = state.NewState() } return &s diff --git a/reaper/reaper.go b/reaper/reaper.go index 558bbed..c01534b 100644 --- a/reaper/reaper.go +++ b/reaper/reaper.go @@ -661,7 +661,7 @@ func reapSecurityGroup(s *reaperaws.SecurityGroup) { // update the internal state if time.Now().After(s.ReaperState().Until) { // if we updated the state, mark it as having been updated - s.ReaperState().SetUpdated(s.IncrementState()) + s.SetUpdated(s.IncrementState()) } log.Notice(fmt.Sprintf("Reapable SecurityGroup discovered: %s.", s.ReapableDescription())) reapables.Put(s.Region, s.ID, s) @@ -671,7 +671,7 @@ func reapCloudformation(c *reaperaws.Cloudformation) { // update the internal state if time.Now().After(c.ReaperState().Until) { // if we updated the state, mark it as having been updated - c.ReaperState().SetUpdated(c.IncrementState()) + c.SetUpdated(c.IncrementState()) } log.Notice(fmt.Sprintf("Reapable Cloudformation discovered: %s.", c.ReapableDescription())) reapables.Put(c.Region, c.ID, c) @@ -681,7 +681,7 @@ func reapInstance(i *reaperaws.Instance) { // update the internal state if time.Now().After(i.ReaperState().Until) { // if we updated the state, mark it as having been updated - i.ReaperState().SetUpdated(i.IncrementState()) + i.SetUpdated(i.IncrementState()) } log.Notice(fmt.Sprintf("Reapable Instance discovered: %s.", i.ReapableDescription())) reapables.Put(i.Region, i.ID, i) @@ -691,7 +691,7 @@ func reapAutoScalingGroup(a *reaperaws.AutoScalingGroup) { // update the internal state if time.Now().After(a.ReaperState().Until) { // if we updated the state, mark it as having been updated - a.ReaperState().SetUpdated(a.IncrementState()) + a.SetUpdated(a.IncrementState()) } log.Notice(fmt.Sprintf("Reapable AutoScalingGroup discovered: %s.", a.ReapableDescription())) reapables.Put(a.Region, a.ID, a) diff --git a/state/state.go b/state/state.go index 07c734c..dfa9c87 100644 --- a/state/state.go +++ b/state/state.go @@ -6,7 +6,8 @@ import ( ) const ( - FirstState StateEnum = iota + InitialState StateEnum = iota + FirstState SecondState ThirdState FinalState @@ -45,10 +46,6 @@ type State struct { Until time.Time } -func (s *State) SetUpdated(b bool) { - s.Updated = b -} - func (s *State) String() string { return s.State.String() + s.reaperTagSeparator + s.Until.Format(s.reaperTagTimeFormat) } @@ -56,7 +53,7 @@ func (s *State) String() string { func NewState() *State { // default return &State{ - State: FirstState, + State: InitialState, Until: time.Now(), reaperTagSeparator: "|", reaperTagTimeFormat: "2006-01-02 03:04PM MST", @@ -66,7 +63,7 @@ func NewState() *State { func NewStateWithUntil(until time.Time) *State { // default return &State{ - State: FirstState, + State: InitialState, Until: until, reaperTagSeparator: "|", reaperTagTimeFormat: "2006-01-02 03:04PM MST", diff --git a/state/stateenum_string.go b/state/stateenum_string.go index 96fc14f..a9d336c 100644 --- a/state/stateenum_string.go +++ b/state/stateenum_string.go @@ -4,9 +4,9 @@ package state import "fmt" -const _StateEnum_name = "FirstStateSecondStateThirdStateFinalStateIgnoreState" +const _StateEnum_name = "InitialStateFirstStateSecondStateThirdStateFinalStateIgnoreState" -var _StateEnum_index = [...]uint8{0, 10, 21, 31, 41, 52} +var _StateEnum_index = [...]uint8{0, 12, 22, 33, 43, 53, 64} func (i StateEnum) String() string { if i < 0 || i >= StateEnum(len(_StateEnum_index)-1) { From 0fe059d7dc4218d71617a52c38c8d4312e8fa3df Mon Sep 17 00:00:00 2001 From: Miles Crabill Date: Tue, 4 Aug 2015 15:11:52 -0700 Subject: [PATCH 2/4] add initial FILTERS.md, modify some filters to make more sense --- aws/FILTERS.md | 174 ++++++++++++++++++++++++++++++++++++++++++++ aws/autoscaling.go | 4 + aws/instance.go | 26 +------ config/default.toml | 4 +- 4 files changed, 183 insertions(+), 25 deletions(-) create mode 100644 aws/FILTERS.md diff --git a/aws/FILTERS.md b/aws/FILTERS.md new file mode 100644 index 0000000..86e8773 --- /dev/null +++ b/aws/FILTERS.md @@ -0,0 +1,174 @@ +# Filters + +## About Filters + +Filters are implemented per resource. To use a filter for a resource, add it to a filtergroup in that resource's section of your configuration file. + +For example: + +``` +[AutoScalingGroups] + Enabled = false + + [AutoScalingGroups.FilterGroups] + [AutoScalingGroups.FilterGroups.ExampleGroup] + [AutoScalingGroups.FilterGroups.ExampleGroup.SizeGreaterThan1] + function = "SizeGreaterThanOrEqualTo" + arguments = ["1"] +``` + +Here, we have a filter called `SizeGreaterThan1`, which calls the function `SizeGreaterThanOrEqualTo` with the arguments `["1"]`. `SizeGreaterThan1` is in a filtergroup called `ExampleGroup`. + +_All filters take an array of arguments. Many filters take a single argument. All arguments are quoted._ + +## Filter Types: + +#### Boolean Filters: + +These filters take a single argument, a string that is parsed to a boolean value that is compared with the resource's value for the specified function. This boolean value can be anything parseable by Go's strconv.ParseBool. See: http://golang.org/pkg/strconv/#ParseBool. + +#### String Filters: + +These filters take a single argument (except where noted), a string that is compared with the resource's value for the specified function. + +#### Time Filters: + +These filters take a single argument, a string that is parsed to a time in RFC3339 format (parseable by http://godoc.org/time#Parse) or a duration (see: http://godoc.org/time#ParseDuration). + +#### Integer Filters: + +These filters take a single argument, a string that is parsed to an int64 and compared with the resource's value for the specified function. + +## Shared Filters (All Resource Types) + +#### Boolean Filters: + +- InCloudformation + + Whether the resource is in a Cloudformation (directly) +- IsDependency + + Whether the resource is a dependency for another resource (a bit abstract) + + Currently, a resource is a dependency if any of the following are satisfied: + * the resource is in the list of resources of a Cloudformation + * the resource is in an AutoScalingGroup + * the resource is a SecurityGroup used by an Instance + +#### String Filters: + +- Tagged + + True if the resource has a tag equal to the input string +- NotTagged + + True if the resource does not have a tag equal to the input string +- Tag (takes two arguments) + + argument 1: the key of a tag + + argument 2: the value of that tag + + True if the resource has a tag equal to the first argument with a value equal to the second +- TagNotEqual (takes two arguments) + + argument 1: the key of a tag + + argument 2: the value of that tag + + True if the resource does not have a tag equal to the first argument with a value equal to the second +- Region + + True if the resource's region matches the input string +- NotRegion + + True if the resource's region does not match the input string +- ReaperState: + + True if the resource's ReaperState is equal to the input string + + One of: + * FirstState + * SecondState + * ThirdState + * FinalState + * IgnoreState +- NameContains: + + True if the resource's name contains the input string + +## Instance Only Filters: + +#### Boolean Filters: + +- HasPublicIPAddress + + True if the Instance has a public IP address +- AutoScaled + + True if the Instance is in an AutoScalingGroup + +#### String Filters: + +- InstanceType + + True if the InstanceType of the Instance matches the input string +- State + + True if the Instance's State matches the input string + + One of: + * pending + * running + * shutting-down + * terminated + * stopping + * stopped +- PublicIPAddress + + True if the public IP address of the instance matches the input string + +#### Time Filters: + +- LaunchTimeBefore + + True if the Instance's LaunchTime is before time input time +- LaunchTimeAfter + + True if the Instance's LaunchTime is after time input time +- LaunchTimeInTheLast + + True if the Instance's LaunchTime is within the input duration +- LaunchTimeNotInTheLast + + True if the Instance's LaunchTime is not within the input duration + + +## AutoScalingGroup Only Filters + +#### Time Filters: + +- CreatedTimeInTheLast + + True if the AutoScalingGroup's CreatedTime is within the input duration +- CreatedTimeNotInTheLast + + True if the AutoScalingGroup's CreatedTime is not within the input duration + +#### Integer Filters: + +- SizeGreaterThan + + True if the AutoScalingGroup's DesiredCapacity is greater than the input size +- SizeLessThan + + True if the AutoScalingGroup's DesiredCapacity is less than the input size +- SizeEqualTo + + True if the AutoScalingGroup's DesiredCapacity is equal to the input size +- SizeLessThanOrEqualTo + + True if the AutoScalingGroup's DesiredCapacity is less than or equal to the input size +- SizeGreaterThanOrEqualTo + + True if the AutoScalingGroup's DesiredCapacity is greater than or equal to the input size + +## Cloudformation Only Filters + +#### String Filters: + +- Status + + True if the Status of the Cloudformation matches the input string + * 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 +- NotStatus + + True if the Status of the Cloudformation does not match the input string + +#### Time Filters: + +- CreatedTimeInTheLast + + True if the AutoScalingGroup's CreatedTime is within the input duration +- CreatedTimeNotInTheLast + + True if the AutoScalingGroup's CreatedTime is not within the input duration diff --git a/aws/autoscaling.go b/aws/autoscaling.go index 681c3dd..cc1cb8c 100644 --- a/aws/autoscaling.go +++ b/aws/autoscaling.go @@ -447,6 +447,10 @@ func (a *AutoScalingGroup) Filter(filter filters.Filter) bool { if a.Tag(filter.Arguments[0]) != filter.Arguments[1] { matched = true } + case "Tag": + if a.Tag(filter.Arguments[0]) == filter.Arguments[1] { + matched = true + } case "Region": for region := range filter.Arguments { if a.Region == reapable.Region(region) { diff --git a/aws/instance.go b/aws/instance.go index 8939dc8..942b2a0 100644 --- a/aws/instance.go +++ b/aws/instance.go @@ -360,28 +360,8 @@ func (i *Instance) Filter(filter filters.Filter) bool { matched := false // map function names to function calls switch filter.Function { - case "Pending": - if b, err := filter.BoolValue(0); err == nil && i.Pending() == b { - matched = true - } - case "Running": - if b, err := filter.BoolValue(0); err == nil && i.Running() == b { - matched = true - } - case "ShuttingDown": - if b, err := filter.BoolValue(0); err == nil && i.ShuttingDown() == b { - matched = true - } - case "Terminated": - if b, err := filter.BoolValue(0); err == nil && i.Terminated() == b { - matched = true - } - case "Stopping": - if b, err := filter.BoolValue(0); err == nil && i.Stopping() == b { - matched = true - } - case "Stopped": - if b, err := filter.BoolValue(0); err == nil && i.Stopped() == b { + case "State": + if i.State != nil && *i.State.Name == filter.Arguments[0] { matched = true } case "InstanceType": @@ -405,7 +385,7 @@ func (i *Instance) Filter(filter filters.Filter) bool { matched = true } case "HasPublicIPAddress": - if i.PublicIPAddress != nil { + if b, err := filter.BoolValue(0); err == nil && b == (i.PublicIPAddress != nil) { matched = true } case "PublicIPAddress": diff --git a/config/default.toml b/config/default.toml index df2c221..4f0cff7 100644 --- a/config/default.toml +++ b/config/default.toml @@ -120,8 +120,8 @@ EventTag = "env:default" function = "LaunchTimeNotInTheLast" arguments = ["24h"] [Instances.FilterGroups.1.2] - function = "Running" - arguments = ["true"] + function = "State" + arguments = ["running"] [Instances.FilterGroups.1.4] function = "IsDependency" arguments = ["false"] From 282e2dd09868a5ba24575f8f0f6231d69affd604 Mon Sep 17 00:00:00 2001 From: Miles Crabill Date: Tue, 4 Aug 2015 15:28:09 -0700 Subject: [PATCH 3/4] make shared filters consistent across resources --- aws/FILTERS.md | 22 ++++++++--- aws/autoscaling.go | 63 ++++++++++++++++++++----------- aws/cloudformation.go | 49 +++++++++++++++++++++--- aws/instance.go | 87 ++++++++++++++++++++++++++----------------- aws/securitygroup.go | 37 ++++++++++++++++-- 5 files changed, 186 insertions(+), 72 deletions(-) diff --git a/aws/FILTERS.md b/aws/FILTERS.md index 86e8773..27e8a08 100644 --- a/aws/FILTERS.md +++ b/aws/FILTERS.md @@ -43,8 +43,6 @@ These filters take a single argument, a string that is parsed to an int64 and co #### Boolean Filters: -- InCloudformation - + Whether the resource is in a Cloudformation (directly) - IsDependency + Whether the resource is a dependency for another resource (a bit abstract) + Currently, a resource is a dependency if any of the following are satisfied: @@ -66,7 +64,7 @@ These filters take a single argument, a string that is parsed to an int64 and co + argument 1: the key of a tag + argument 2: the value of that tag + True if the resource does not have a tag equal to the first argument with a value equal to the second -- Region +- Region (takes any number of arguments) + True if the resource's region matches the input string - NotRegion + True if the resource's region does not match the input string @@ -78,13 +76,23 @@ These filters take a single argument, a string that is parsed to an int64 and co * ThirdState * FinalState * IgnoreState +- NotReaperState: + + True if the resource's ReaperState is not equal to the input string - NameContains: + True if the resource's name contains the input string +- NotNameContains: + + True if the resource's name does not contain the input string +- Named: + + True if the resource's name is equal to the input string +- NotNamed: + + True if the resource's name is not equal to the input string ## Instance Only Filters: #### Boolean Filters: +- InCloudformation + + Whether the Instance is in a Cloudformation (directly) - HasPublicIPAddress + True if the Instance has a public IP address - AutoScaled @@ -104,7 +112,7 @@ These filters take a single argument, a string that is parsed to an int64 and co * stopping * stopped - PublicIPAddress - + True if the public IP address of the instance matches the input string + + True if the public IP address of the Instance matches the input string #### Time Filters: @@ -122,6 +130,8 @@ These filters take a single argument, a string that is parsed to an int64 and co #### Time Filters: +- InCloudformation + + Whether the AutoScalingGroup is in a Cloudformation (directly) - CreatedTimeInTheLast + True if the AutoScalingGroup's CreatedTime is within the input duration - CreatedTimeNotInTheLast @@ -169,6 +179,6 @@ These filters take a single argument, a string that is parsed to an int64 and co #### Time Filters: - CreatedTimeInTheLast - + True if the AutoScalingGroup's CreatedTime is within the input duration + + True if the Cloudformation's CreatedTime is within the input duration - CreatedTimeNotInTheLast - + True if the AutoScalingGroup's CreatedTime is not within the input duration + + True if the Cloudformation's CreatedTime is not within the input duration diff --git a/aws/autoscaling.go b/aws/autoscaling.go index cc1cb8c..fcca6e0 100644 --- a/aws/autoscaling.go +++ b/aws/autoscaling.go @@ -435,46 +435,63 @@ func (a *AutoScalingGroup) Filter(filter filters.Filter) bool { if i, err := filter.Int64Value(0); err == nil && a.sizeGreaterThanOrEqualTo(i) { matched = true } - case "Tagged": - if a.Tagged(filter.Arguments[0]) { - matched = true - } - case "NotTagged": - if !a.Tagged(filter.Arguments[0]) { + case "CreatedTimeInTheLast": + d, err := time.ParseDuration(filter.Arguments[0]) + if err == nil && a.CreatedTime != nil && time.Since(*a.CreatedTime) < d { matched = true } - case "TagNotEqual": - if a.Tag(filter.Arguments[0]) != filter.Arguments[1] { + case "CreatedTimeNotInTheLast": + d, err := time.ParseDuration(filter.Arguments[0]) + if err == nil && a.CreatedTime != nil && time.Since(*a.CreatedTime) > d { matched = true } - case "Tag": - if a.Tag(filter.Arguments[0]) == filter.Arguments[1] { + case "InCloudformation": + if b, err := filter.BoolValue(0); err == nil && a.IsInCloudformation == b { matched = true } case "Region": - for region := range filter.Arguments { + for _, region := range filter.Arguments { if a.Region == reapable.Region(region) { matched = true } } case "NotRegion": - for region := range filter.Arguments { + // 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) { - matched = false + regionSpecified = true } } - case "CreatedTimeInTheLast": - d, err := time.ParseDuration(filter.Arguments[0]) - if err == nil && a.CreatedTime != nil && time.Since(*a.CreatedTime) < d { + if !regionSpecified { matched = true } - case "CreatedTimeNotInTheLast": - d, err := time.ParseDuration(filter.Arguments[0]) - if err == nil && a.CreatedTime != nil && time.Since(*a.CreatedTime) > d { + case "Tagged": + if a.Tagged(filter.Arguments[0]) { matched = true } - case "InCloudformation": - if b, err := filter.BoolValue(0); err == nil && a.IsInCloudformation == b { + 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": @@ -485,6 +502,10 @@ func (a *AutoScalingGroup) Filter(filter filters.Filter) bool { 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)) } diff --git a/aws/cloudformation.go b/aws/cloudformation.go index b7b4a69..af58f67 100644 --- a/aws/cloudformation.go +++ b/aws/cloudformation.go @@ -270,6 +270,33 @@ func (a *Cloudformation) Filter(filter filters.Filter) bool { 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 @@ -282,14 +309,20 @@ func (a *Cloudformation) Filter(filter filters.Filter) bool { if a.Tag(filter.Arguments[0]) != filter.Arguments[1] { matched = true } - case "CreatedTimeInTheLast": - d, err := time.ParseDuration(filter.Arguments[0]) - if err == nil && a.CreationTime != nil && time.Since(*a.CreationTime) < d { + case "ReaperState": + if a.reaperState.State.String() == filter.Arguments[0] { matched = true } - case "CreatedTimeNotInTheLast": - d, err := time.ParseDuration(filter.Arguments[0]) - if err == nil && a.CreationTime != nil && time.Since(*a.CreationTime) > d { + 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": @@ -300,6 +333,10 @@ func (a *Cloudformation) Filter(filter filters.Filter) bool { 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)) } diff --git a/aws/instance.go b/aws/instance.go index 942b2a0..58f6173 100644 --- a/aws/instance.go +++ b/aws/instance.go @@ -356,94 +356,111 @@ func (i *Instance) AWSConsoleURL() *url.URL { return url } -func (i *Instance) Filter(filter filters.Filter) bool { +func (a *Instance) Filter(filter filters.Filter) bool { matched := false // map function names to function calls switch filter.Function { case "State": - if i.State != nil && *i.State.Name == filter.Arguments[0] { + if a.State != nil && *a.State.Name == filter.Arguments[0] { matched = true } case "InstanceType": - if i.InstanceType != nil && *i.InstanceType == filter.Arguments[0] { + if a.InstanceType != nil && *a.InstanceType == filter.Arguments[0] { matched = true } - case "Tagged": - if i.Tagged(filter.Arguments[0]) { - matched = true - } - case "NotTagged": - if !i.Tagged(filter.Arguments[0]) { - matched = true - } - case "Tag": - if i.Tag(filter.Arguments[0]) == filter.Arguments[1] { + case "HasPublicIPAddress": + if b, err := filter.BoolValue(0); err == nil && b == (a.PublicIPAddress != nil) { matched = true } - case "TagNotEqual": - if i.Tag(filter.Arguments[0]) != filter.Arguments[1] { + case "PublicIPAddress": + if a.PublicIPAddress != nil && *a.PublicIPAddress == filter.Arguments[0] { matched = true } - case "HasPublicIPAddress": - if b, err := filter.BoolValue(0); err == nil && b == (i.PublicIPAddress != nil) { + case "InCloudformation": + if b, err := filter.BoolValue(0); err == nil && a.IsInCloudformation == b { matched = true } - case "PublicIPAddress": - if i.PublicIPAddress != nil && *i.PublicIPAddress == filter.Arguments[0] { + 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 && i.LaunchTime != nil && t.After(*i.LaunchTime) { + 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 && i.LaunchTime != nil && t.Before(*i.LaunchTime) { + if err == nil && a.LaunchTime != nil && t.Before(*a.LaunchTime) { matched = true } case "LaunchTimeInTheLast": d, err := time.ParseDuration(filter.Arguments[0]) - if err == nil && i.LaunchTime != nil && time.Since(*i.LaunchTime) < d { + 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 && i.LaunchTime != nil && time.Since(*i.LaunchTime) > d { + if err == nil && a.LaunchTime != nil && time.Since(*a.LaunchTime) > d { matched = true } case "Region": - for region := range filter.Arguments { - if i.Region == reapable.Region(region) { + for _, region := range filter.Arguments { + if a.Region == reapable.Region(region) { matched = true } } case "NotRegion": - for region := range filter.Arguments { - if i.Region == reapable.Region(region) { - matched = false + // 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 i.reaperState.State.String() == filter.Arguments[0] { + if a.reaperState.State.String() == filter.Arguments[0] { matched = true } - case "InCloudformation": - if b, err := filter.BoolValue(0); err == nil && i.IsInCloudformation == b { + case "NotReaperState": + if a.reaperState.State.String() != filter.Arguments[0] { matched = true } - case "AutoScaled": - if b, err := filter.BoolValue(0); err == nil && i.AutoScaled == b { + 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 && i.Dependency == b { + if b, err := filter.BoolValue(0); err == nil && a.Dependency == b { matched = true } case "NameContains": - if strings.Contains(i.Name, filter.Arguments[0]) { + if strings.Contains(a.Name, filter.Arguments[0]) { + matched = true + } + case "NotNameContains": + if !strings.Contains(a.Name, filter.Arguments[0]) { matched = true } default: diff --git a/aws/securitygroup.go b/aws/securitygroup.go index 9ca7148..ce976f1 100644 --- a/aws/securitygroup.go +++ b/aws/securitygroup.go @@ -208,6 +208,27 @@ 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 @@ -220,6 +241,14 @@ func (a *SecurityGroup) Filter(filter filters.Filter) bool { 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 @@ -228,10 +257,6 @@ func (a *SecurityGroup) Filter(filter filters.Filter) bool { if a.Name != filter.Arguments[0] { matched = true } - case "InCloudformation": - if b, err := filter.BoolValue(0); err == nil && a.IsInCloudformation == b { - matched = true - } case "IsDependency": if b, err := filter.BoolValue(0); err == nil && a.Dependency == b { matched = true @@ -240,6 +265,10 @@ func (a *SecurityGroup) Filter(filter filters.Filter) bool { 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)) } From 93579b5ad51ffe4cf2fd37c733d9bc6308ebe5e0 Mon Sep 17 00:00:00 2001 From: Miles Crabill Date: Sun, 23 Aug 2015 10:35:20 -0700 Subject: [PATCH 4/4] fix link generation using reapable ID twice instead of reapable region --- aws/http.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aws/http.go b/aws/http.go index 17d1990..7f8faad 100644 --- a/aws/http.go +++ b/aws/http.go @@ -23,7 +23,7 @@ func MakeScheduleLink(region reapable.Region, id reapable.ID, tokenSecret, apiUR func MakeTerminateLink(region reapable.Region, id reapable.ID, tokenSecret, apiURL string) (string, error) { term, err := token.Tokenize(tokenSecret, - token.NewTerminateJob(id.String(), id.String())) + token.NewTerminateJob(region.String(), id.String())) if err != nil { return "", err @@ -35,7 +35,7 @@ func MakeTerminateLink(region reapable.Region, id reapable.ID, tokenSecret, apiU func MakeIgnoreLink(region reapable.Region, id reapable.ID, tokenSecret, apiURL string, duration time.Duration) (string, error) { delay, err := token.Tokenize(tokenSecret, - token.NewDelayJob(id.String(), id.String(), + token.NewDelayJob(region.String(), id.String(), duration)) if err != nil {