From d040c4ec6dfb334ccf7a229ec7f2bce1482bde75 Mon Sep 17 00:00:00 2001 From: Erik Kristensen Date: Fri, 7 Jun 2024 21:42:50 -0600 Subject: [PATCH 1/4] feat(ec2-image): include disabled and deprecated images --- resources/ec2-image.go | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/resources/ec2-image.go b/resources/ec2-image.go index 665ac1de..cefcd4fa 100644 --- a/resources/ec2-image.go +++ b/resources/ec2-image.go @@ -33,6 +33,8 @@ func (l *EC2ImageLister) List(_ context.Context, o interface{}) ([]resource.Reso Owners: []*string{ aws.String("self"), }, + IncludeDeprecated: aws.Bool(true), + IncludeDisabled: aws.Bool(true), } resp, err := svc.DescribeImages(params) if err != nil { @@ -42,11 +44,13 @@ func (l *EC2ImageLister) List(_ context.Context, o interface{}) ([]resource.Reso resources := make([]resource.Resource, 0) for _, out := range resp.Images { resources = append(resources, &EC2Image{ - svc: svc, - creationDate: *out.CreationDate, - id: *out.ImageId, - name: *out.Name, - tags: out.Tags, + svc: svc, + creationDate: *out.CreationDate, + id: *out.ImageId, + name: *out.Name, + tags: out.Tags, + state: out.State, + deprecatedTime: out.DeprecationTime, }) } @@ -54,11 +58,14 @@ func (l *EC2ImageLister) List(_ context.Context, o interface{}) ([]resource.Reso } type EC2Image struct { - svc *ec2.EC2 - creationDate string - id string - name string - tags []*ec2.Tag + svc *ec2.EC2 + creationDate string + id string + name string + tags []*ec2.Tag + state *string + deprecated *bool + deprecatedTime *string } func (e *EC2Image) Remove(_ context.Context) error { @@ -73,6 +80,9 @@ func (e *EC2Image) Properties() types.Properties { properties.Set("CreationDate", e.creationDate) properties.Set("Name", e.name) + properties.Set("State", e.state) + properties.Set("Deprecated", e.deprecated) + properties.Set("DeprecatedTime", e.deprecatedTime) for _, tagValue := range e.tags { properties.SetTag(tagValue.Key, tagValue.Value) From 2b56ad94140f2dbfc47252e373eb225a4beebc51 Mon Sep 17 00:00:00 2001 From: Erik Kristensen Date: Sat, 8 Jun 2024 11:12:34 -0600 Subject: [PATCH 2/4] feat(ec2-image): add disable deregistration protection setting, better filters --- resources/ec2-image.go | 102 +++++++++++++++++++++++++++++++++-------- 1 file changed, 82 insertions(+), 20 deletions(-) diff --git a/resources/ec2-image.go b/resources/ec2-image.go index cefcd4fa..58e60039 100644 --- a/resources/ec2-image.go +++ b/resources/ec2-image.go @@ -2,12 +2,16 @@ package resources import ( "context" + "fmt" + + "github.com/gotidy/ptr" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/ekristen/libnuke/pkg/registry" "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/settings" "github.com/ekristen/libnuke/pkg/types" "github.com/ekristen/aws-nuke/pkg/nuke" @@ -15,17 +19,27 @@ import ( const EC2ImageResource = "EC2Image" +const IncludeDeprecatedSetting = "IncludeDeprecated" +const IncludeDisabledSetting = "IncludeDisabled" +const DisableDeregistrationProtectionSetting = "DisableDeregistrationProtection" + func init() { registry.Register(®istry.Registration{ Name: EC2ImageResource, Scope: nuke.Account, Lister: &EC2ImageLister{}, + Settings: []string{ + DisableDeregistrationProtectionSetting, + IncludeDeprecatedSetting, + IncludeDisabledSetting, + }, }) } type EC2ImageLister struct{} func (l *EC2ImageLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { + resources := make([]resource.Resource, 0) opts := o.(*nuke.ListerOpts) svc := ec2.New(opts.Session) @@ -33,24 +47,25 @@ func (l *EC2ImageLister) List(_ context.Context, o interface{}) ([]resource.Reso Owners: []*string{ aws.String("self"), }, - IncludeDeprecated: aws.Bool(true), - IncludeDisabled: aws.Bool(true), + IncludeDeprecated: ptr.Bool(true), + IncludeDisabled: ptr.Bool(true), } resp, err := svc.DescribeImages(params) if err != nil { return nil, err } - resources := make([]resource.Resource, 0) for _, out := range resp.Images { resources = append(resources, &EC2Image{ - svc: svc, - creationDate: *out.CreationDate, - id: *out.ImageId, - name: *out.Name, - tags: out.Tags, - state: out.State, - deprecatedTime: out.DeprecationTime, + svc: svc, + id: out.ImageId, + name: out.Name, + tags: out.Tags, + state: out.State, + creationDate: out.CreationDate, + deprecated: ptr.Bool(out.DeprecationTime != nil), + deprecatedTime: out.DeprecationTime, + deregistrationProtection: out.DeregistrationProtection, }) } @@ -58,23 +73,65 @@ func (l *EC2ImageLister) List(_ context.Context, o interface{}) ([]resource.Reso } type EC2Image struct { - svc *ec2.EC2 - creationDate string - id string - name string - tags []*ec2.Tag - state *string - deprecated *bool - deprecatedTime *string + svc *ec2.EC2 + settings *settings.Setting + + id *string + name *string + tags []*ec2.Tag + state *string + deprecated *bool + deprecatedTime *string + creationDate *string + deregistrationProtection *string +} + +func (e *EC2Image) Filter() error { + if *e.state == "pending" { + return fmt.Errorf("ineligible state for removal") + } + + if *e.deregistrationProtection != "disabled" { + if e.settings.Get(DisableDeregistrationProtectionSetting) == nil || + (e.settings.Get(DisableDeregistrationProtectionSetting) != nil && + !e.settings.Get(DisableDeregistrationProtectionSetting).(bool)) { + return fmt.Errorf("deregistration protection is enabled") + } + } + + if !e.settings.Get(IncludeDeprecatedSetting).(bool) && e.deprecated != nil && *e.deprecated { + return fmt.Errorf("excluded by %s setting being false", IncludeDeprecatedSetting) + } + + if !e.settings.Get(IncludeDisabledSetting).(bool) && e.state != nil && *e.state == "disabled" { + return fmt.Errorf("excluded by %s setting being false", IncludeDisabledSetting) + } + + return nil } func (e *EC2Image) Remove(_ context.Context) error { + if err := e.removeDeregistrationProtection(); err != nil { + return err + } + _, err := e.svc.DeregisterImage(&ec2.DeregisterImageInput{ - ImageId: &e.id, + ImageId: e.id, }) + return err } +func (e *EC2Image) removeDeregistrationProtection() error { + res, _ := e.svc.DisableImageDeregistrationProtectionRequest(&ec2.DisableImageDeregistrationProtectionInput{ + ImageId: e.id, + }) + if res.Error != nil { + return res.Error + } + return nil +} + func (e *EC2Image) Properties() types.Properties { properties := types.NewProperties() @@ -83,6 +140,7 @@ func (e *EC2Image) Properties() types.Properties { properties.Set("State", e.state) properties.Set("Deprecated", e.deprecated) properties.Set("DeprecatedTime", e.deprecatedTime) + properties.Set("DeregistrationProtection", e.deregistrationProtection) for _, tagValue := range e.tags { properties.SetTag(tagValue.Key, tagValue.Value) @@ -91,5 +149,9 @@ func (e *EC2Image) Properties() types.Properties { } func (e *EC2Image) String() string { - return e.id + return *e.id +} + +func (e *EC2Image) Settings(settings *settings.Setting) { + e.settings = settings } From 8401aaba6d44851737b11953128cc89d7197d182 Mon Sep 17 00:00:00 2001 From: Erik Kristensen Date: Sat, 8 Jun 2024 18:55:56 -0600 Subject: [PATCH 3/4] fix(ec2-image): disable image deregistration protection --- resources/ec2-image.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/resources/ec2-image.go b/resources/ec2-image.go index 58e60039..3c610e33 100644 --- a/resources/ec2-image.go +++ b/resources/ec2-image.go @@ -11,7 +11,7 @@ import ( "github.com/ekristen/libnuke/pkg/registry" "github.com/ekristen/libnuke/pkg/resource" - "github.com/ekristen/libnuke/pkg/settings" + libsettings "github.com/ekristen/libnuke/pkg/settings" "github.com/ekristen/libnuke/pkg/types" "github.com/ekristen/aws-nuke/pkg/nuke" @@ -74,7 +74,7 @@ func (l *EC2ImageLister) List(_ context.Context, o interface{}) ([]resource.Reso type EC2Image struct { svc *ec2.EC2 - settings *settings.Setting + settings *libsettings.Setting id *string name *string @@ -91,7 +91,7 @@ func (e *EC2Image) Filter() error { return fmt.Errorf("ineligible state for removal") } - if *e.deregistrationProtection != "disabled" { + if *e.deregistrationProtection != ec2.ImageStateDisabled { if e.settings.Get(DisableDeregistrationProtectionSetting) == nil || (e.settings.Get(DisableDeregistrationProtectionSetting) != nil && !e.settings.Get(DisableDeregistrationProtectionSetting).(bool)) { @@ -103,7 +103,7 @@ func (e *EC2Image) Filter() error { return fmt.Errorf("excluded by %s setting being false", IncludeDeprecatedSetting) } - if !e.settings.Get(IncludeDisabledSetting).(bool) && e.state != nil && *e.state == "disabled" { + if !e.settings.Get(IncludeDisabledSetting).(bool) && e.state != nil && *e.state == ec2.ImageStateDisabled { return fmt.Errorf("excluded by %s setting being false", IncludeDisabledSetting) } @@ -123,13 +123,18 @@ func (e *EC2Image) Remove(_ context.Context) error { } func (e *EC2Image) removeDeregistrationProtection() error { - res, _ := e.svc.DisableImageDeregistrationProtectionRequest(&ec2.DisableImageDeregistrationProtectionInput{ + if *e.deregistrationProtection == ec2.ImageStateDisabled { + return nil + } + + if !e.settings.Get(DisableDeregistrationProtectionSetting).(bool) { + return nil + } + + _, err := e.svc.DisableImageDeregistrationProtection(&ec2.DisableImageDeregistrationProtectionInput{ ImageId: e.id, }) - if res.Error != nil { - return res.Error - } - return nil + return err } func (e *EC2Image) Properties() types.Properties { @@ -152,6 +157,6 @@ func (e *EC2Image) String() string { return *e.id } -func (e *EC2Image) Settings(settings *settings.Setting) { +func (e *EC2Image) Settings(settings *libsettings.Setting) { e.settings = settings } From 0b26c14bb9df7ea93ac9736c571f33bde3fff5c1 Mon Sep 17 00:00:00 2001 From: Erik Kristensen Date: Sat, 8 Jun 2024 19:16:32 -0600 Subject: [PATCH 4/4] fix(ec2-image): improve handling of deregistration cooldown --- resources/ec2-image.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/resources/ec2-image.go b/resources/ec2-image.go index 3c610e33..9e0db2c5 100644 --- a/resources/ec2-image.go +++ b/resources/ec2-image.go @@ -3,6 +3,7 @@ package resources import ( "context" "fmt" + "strings" "github.com/gotidy/ptr" @@ -56,12 +57,18 @@ func (l *EC2ImageLister) List(_ context.Context, o interface{}) ([]resource.Reso } for _, out := range resp.Images { + visibility := "Private" + if ptr.ToBool(out.Public) { + visibility = "Public" + } + resources = append(resources, &EC2Image{ svc: svc, id: out.ImageId, name: out.Name, tags: out.Tags, state: out.State, + visibility: ptr.String(visibility), creationDate: out.CreationDate, deprecated: ptr.Bool(out.DeprecationTime != nil), deprecatedTime: out.DeprecationTime, @@ -80,6 +87,7 @@ type EC2Image struct { name *string tags []*ec2.Tag state *string + visibility *string deprecated *bool deprecatedTime *string creationDate *string @@ -91,6 +99,11 @@ func (e *EC2Image) Filter() error { return fmt.Errorf("ineligible state for removal") } + if strings.HasPrefix(*e.deregistrationProtection, "disabled after") { + return fmt.Errorf("would remove after %s due to deregistration protection cooldown", + strings.ReplaceAll(*e.deregistrationProtection, "disabled after ", "")) + } + if *e.deregistrationProtection != ec2.ImageStateDisabled { if e.settings.Get(DisableDeregistrationProtectionSetting) == nil || (e.settings.Get(DisableDeregistrationProtectionSetting) != nil && @@ -115,6 +128,10 @@ func (e *EC2Image) Remove(_ context.Context) error { return err } + if *e.deregistrationProtection == "enabled-with-cooldown" { + return nil + } + _, err := e.svc.DeregisterImage(&ec2.DeregisterImageInput{ ImageId: e.id, }) @@ -143,6 +160,7 @@ func (e *EC2Image) Properties() types.Properties { properties.Set("CreationDate", e.creationDate) properties.Set("Name", e.name) properties.Set("State", e.state) + properties.Set("Visibility", e.visibility) properties.Set("Deprecated", e.deprecated) properties.Set("DeprecatedTime", e.deprecatedTime) properties.Set("DeregistrationProtection", e.deregistrationProtection)