Skip to content

Commit

Permalink
feature: cloud-nuke-after tag to protect the resources (#741)
Browse files Browse the repository at this point in the history
Co-authored-by: James Kwon <[email protected]>
  • Loading branch information
james03160927 and james03160927 authored Jul 12, 2024
1 parent fd71b8f commit 29dec17
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 109 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,11 @@ cloud-nuke aws --resource-type s3 --timeout 10m
```
This will attempt to nuke the specified resources within a 10-minute timeframe.


### Protect Resources with `cloud-nuke-after` Tag
By tagging resources with `cloud-nuke-after` and specifying a future date in ISO 8601 format (e.g., 2024-07-09T00:00:00Z), you can ensure that these resources are protected from accidental or premature deletion until the specified date. This method helps to keep important resources intact until their designated expiration date.


### Using cloud-nuke as a library

You can import cloud-nuke into other projects and use it as a library for programmatically inspecting and counting
Expand Down
3 changes: 3 additions & 0 deletions aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ func GetAllResources(c context.Context, query *Query, configObj config.Config) (
// This function only sets the objects that have the `DefaultOnly` field, currently VPC, Subnet, and Security Group.
configObj.AddEC2DefaultOnly(query.DefaultOnly)

// This will protect dated resources by nuking them until the specified date has passed
configObj.AddProtectUntilExpireFlag(query.ProtectUntilExpire)

account := AwsAccountResources{
Resources: make(map[string]AwsResources),
}
Expand Down
2 changes: 2 additions & 0 deletions aws/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Query struct {
Timeout *time.Duration
ExcludeFirstSeen bool
DefaultOnly bool
ProtectUntilExpire bool
}

// NewQuery configures and returns a Query struct that can be passed into the InspectResources method
Expand All @@ -35,6 +36,7 @@ func NewQuery(regions, excludeRegions, resourceTypes, excludeResourceTypes []str
Timeout: timeout,
DefaultOnly: defaultOnly,
ExcludeFirstSeen: excludeFirstSeen,
ProtectUntilExpire: false,
}

validationErr := q.Validate()
Expand Down
3 changes: 3 additions & 0 deletions commands/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ func awsNuke(c *cli.Context) error {
return errors.WithStackTrace(err)
}

// Enable date checking for protecting the resources
// Note: This should only be enabled with AWS-Nuke, and it will not be enabled with force.
query.ProtectUntilExpire = !c.Bool("force")
return awsNukeHelper(c, configObj, query)
}

Expand Down
72 changes: 67 additions & 5 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ import (
"strings"
"time"

"github.com/gruntwork-io/cloud-nuke/logging"
"gopkg.in/yaml.v2"
)

const DefaultAwsResourceExclusionTagKey = "cloud-nuke-excluded"
const (
DefaultAwsResourceExclusionTagKey = "cloud-nuke-excluded"
CloudNukeAfterExclusionTagKey = "cloud-nuke-after"
CloudNukeAfterTimeFormat = time.RFC3339
CloudNukeAfterTimeFormatLegacy = time.DateTime
)

// Config - the config object we pass around
type Config struct {
Expand Down Expand Up @@ -183,6 +189,30 @@ func (c *Config) addDefautlOnly(flag bool) {
}
}

func (c *Config) addBoolFlag(flag bool, fieldName string) {
// Do nothing if the flag filter is false, by default it will be false
if flag == false {
return
}

v := reflect.ValueOf(c).Elem()
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
if field.Kind() != reflect.Struct {
continue
}

defaultOnlyField := field.FieldByName(fieldName)
// IsValid reports whether v represents a value.
// It returns false if v is the zero Value.
// If IsValid returns false, all other methods except String panic.
if defaultOnlyField.IsValid() {
defaultOnlyVal := defaultOnlyField.Addr().Interface().(*bool)
*defaultOnlyVal = flag
}
}
}

func (c *Config) AddIncludeAfterTime(includeAfter *time.Time) {
// include after filter has been applied to all resources via `newer-than` flag, we are
// setting this rule across all resource types.
Expand All @@ -204,7 +234,12 @@ func (c *Config) AddTimeout(timeout *time.Duration) {
func (c *Config) AddEC2DefaultOnly(flag bool) {
// The flag filter has been applied to all resources via the default-only flag.
// We are now setting this rule across all resource types that have a field named `DefaultOnly`.
c.addDefautlOnly(flag)
c.addBoolFlag(flag, "DefaultOnly")
}

func (c *Config) AddProtectUntilExpireFlag(flag bool) {
// We are now setting this rule across all resource types that have a field named `ProtectUntilExpire`.
c.addBoolFlag(flag, "ProtectUntilExpire")
}

type KMSCustomerKeyResourceType struct {
Expand All @@ -217,9 +252,10 @@ type EC2ResourceType struct {
}

type ResourceType struct {
IncludeRule FilterRule `yaml:"include"`
ExcludeRule FilterRule `yaml:"exclude"`
Timeout string `yaml:"timeout"`
IncludeRule FilterRule `yaml:"include"`
ExcludeRule FilterRule `yaml:"exclude"`
Timeout string `yaml:"timeout"`
ProtectUntilExpire bool `yaml:"protect_until_expire"`
}

type FilterRule struct {
Expand Down Expand Up @@ -327,6 +363,20 @@ func (r ResourceType) getExclusionTag() string {
return DefaultAwsResourceExclusionTagKey
}

func ParseTimestamp(timestamp string) (*time.Time, error) {
parsed, err := time.Parse(CloudNukeAfterTimeFormat, timestamp)
if err != nil {
logging.Debugf("Error parsing the timestamp into a `%v` Time format. Trying parsing the timestamp using the legacy `time.DateTime` format.", CloudNukeAfterTimeFormat)
parsed, err = time.Parse(CloudNukeAfterTimeFormatLegacy, timestamp)
if err != nil {
logging.Debugf("Error parsing the timestamp into legacy `time.DateTime` Time format")
return nil, err
}
}

return &parsed, nil
}

func (r ResourceType) ShouldIncludeBasedOnTag(tags map[string]string) bool {
// Handle exclude rule first
exclusionTag := r.getExclusionTag()
Expand All @@ -336,6 +386,18 @@ func (r ResourceType) ShouldIncludeBasedOnTag(tags map[string]string) bool {
}
}

if r.ProtectUntilExpire {
// Check if the tags contain "cloud-nuke-after" and if the date is before today.
if value, ok := tags[CloudNukeAfterExclusionTagKey]; ok {
nukeDate, err := ParseTimestamp(value)
if err == nil {
if !nukeDate.Before(time.Now()) {
logging.Debugf("[Skip] the resource is protected until %v", nukeDate)
return false
}
}
}
}
return true
}

Expand Down
Loading

0 comments on commit 29dec17

Please sign in to comment.