Skip to content

Resources

Resources are the core of the tool, they are what is used to list and remove resources from AWS. The resources are broken down into separate files.

When creating a resource there's the base resource type, then there's the Lister type that returns a list of resources that it discovers. Those resources are then filtered by any filtering criteria on the resource itself.

Anatomy of a Resource

The anatomy of a resource is fairly simple, it's broken down into a few parts:

  • Resource - This is the base resource type that is used to define the resource.
  • Lister - This is the type that is used to list the resources.

Resource

The resource must have the func Remove() error method defined on it, this is what is used to remove the resource.

It can optionally have the following methods defined:

  • func Filter() error - This is used to pre-filter resources, usually based on internal criteria, like system defaults.
  • func String() string - This is used to print the resource in a human-readable format.
  • func Properties() types.Properties - This is used to print the resource in a human-readable format.
package resources

import (
    "context"

    "github.com/ekristen/libnuke/pkg/resource"
    "github.com/ekristen/libnuke/pkg/types"

    "github.com/ekristen/aws-nuke/v3/pkg/nuke"
)

type ExampleResource struct {
    ID *string `description:"The ID of the resource"`
}

func (r *ExampleResource) Remove(_ context.Context) error {
    // remove the resource, an error will put the resource in failed state
    // resources in failed state are retried a number of times
    return nil
}

func (r *ExampleResource) Filter() error {
    // filter the resource, this is useful for built-in resources that cannot
    // be removed, like an AWS managed resource, return an error here to filter
    // it before it even gets to the user supplied filters.
    return nil
}

func (r *ExampleResource) String() string {
    // return a string representation of the resource, this is legacy, but still
    // used for a number of reasons.
    return *r.ID
}

func (r *ExampleResource) Properties() types.Properties {
    // return a properties representation of the resource via struct inspection
    return types.NewPropertiesFromStruct(r)
}

Lister

The lister must have the func List(ctx context.Context, o interface{}) ([]resource.Resource, error) method defined on it.

package resources

import (
    "context"

    "github.com/ekristen/libnuke/pkg/resource"

    "github.com/ekristen/aws-nuke/v3/pkg/nuke"
)

type ExampleResourceLister struct{}

func (l *ExampleResourceLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) {
    opts := o.(*nuke.ListerOpts)

    var resources []resource.Resource

    // list the resources and add to resources slice

    return resources, nil
}

Example

package resources

import (
    "context"

    "github.com/ekristen/libnuke/pkg/resource"
    "github.com/ekristen/libnuke/pkg/types"

    "github.com/ekristen/aws-nuke/v3/pkg/nuke"
)

type ExampleResourceLister struct{}

func (l *ExampleResourceLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) {
    opts := o.(*nuke.ListerOpts)

    var resources []resource.Resource

    // list the resources and add to resources slice

    return resources, nil
}

// -----------------------------------------------------------------------------

type ExampleResource struct {
    ID *string `description:"The unique ID of the resource"`
}

func (r *ExampleResource) Remove(_ context.Context) error {
    // remove the resource, an error will put the resource in failed state
    // resources in failed state are retried a number of times
    return nil
}

func (r *ExampleResource) Filter() error {
    // filter the resource, this is useful for built-in resources that cannot
    // be removed, like an AWS managed resource, return an error here to filter
    // it before it even gets to the user supplied filters.
    return nil
}

func (r *ExampleResource) String() string {
    // return a string representation of the resource, this is legacy, but still
    // used for a number of reasons.
    return *r.ID
}

func (r *ExampleResource) Properties() types.Properties {
    // return a properties representation of the resource from struct inspection
    return types.NewPropertiesFromStruct(r)
}

Creating a new resource

Creating a new resources is fairly straightforward and a template is provided for you, along with a tool to help you generate the boilerplate code.

Currently, the code is generated using a tool that is located in tools/create-resource/main.go and can be run like so:

Note

At present, the tool does not check if the service or the resource type is valid, this is purely a helper tool to generate the boilerplate code.

go run tools/create-resource/main.go <service> <resource-type>

This will output the boilerplate code to stdout, so you can copy and paste it into the appropriate file or you can redirect to a file like so:

go run tools/create-resource/main.go <service> <resource-type> > resources/<resource-type>.go

Converting a resource for self documenting

To convert a resource for self documenting, you need to do the following:

  • Write a test for the resource to verify its current properties
  • Add the Resource field to the registration struct
  • Capitalize the first letter of the field names
  • Match the field names to the property names that are defined in Properties() method
  • Switch to NewPropertiesFromStruct method in Properties() method
  • Run the tests to verify the properties are still correct
  • Run the generate-resource-docs command to generate the documentation
  • Commit your changes and open a pull request

Example

Before

package resources

import (
    "context"

    "fmt"

    "github.com/aws/aws-sdk-go/service/sns"

    "github.com/ekristen/libnuke/pkg/registry"
    "github.com/ekristen/libnuke/pkg/resource"
    "github.com/ekristen/libnuke/pkg/types"

    "github.com/ekristen/aws-nuke/v3/pkg/nuke"
)

const SNSTopicResource = "SNSTopic"

func init() {
    registry.Register(&registry.Registration{
        Name:   SNSTopicResource,
        Scope:  nuke.Account,
        Lister: &SNSTopicLister{},
    })
}

type SNSTopic struct {
    svc  *sns.SNS
    id   *string
    tags []*sns.Tag
}

func (r *SNSTopic) Remove(_ context.Context) error {
    _, err := r.svc.DeleteTopic(&sns.DeleteTopicInput{
        TopicArn: r.id,
    })
    return err
}

func (r *SNSTopic) Properties() types.Properties {
    properties := types.NewProperties()

    for _, tag := range r.tags {
        properties.SetTag(tag.Key, tag.Value)
    }
    properties.Set("TopicARN", r.id)

    return properties
}

func (r *SNSTopic) String() string {
    return fmt.Sprintf("TopicARN: %s", *r.id)
}

type SNSTopicLister struct{}

func (l *SNSTopicLister) List(_ context.Context, _ interface{}) ([]resource.Resource, error) {
    return nil, nil
}

After

package resources

import (
    "context"

    "fmt"

    "github.com/aws/aws-sdk-go/service/sns"

    "github.com/ekristen/libnuke/pkg/registry"
    "github.com/ekristen/libnuke/pkg/resource"
    "github.com/ekristen/libnuke/pkg/types"

    "github.com/ekristen/aws-nuke/v3/pkg/nuke"
)

const SNSTopicResource = "SNSTopic"

func init() {
    registry.Register(&registry.Registration{
        Name:     SNSTopicResource,
        Scope:    nuke.Account,
        Resource: &SNSTopic{},
        Lister:   &SNSTopicLister{},
    })
}

type SNSTopic struct {
    svc      *sns.SNS
    TopicARN *string `description:"The ARN of the SNS Topic"`
    Tags     []*sns.Tag
}

func (r *SNSTopic) Remove(_ context.Context) error {
    _, err := r.svc.DeleteTopic(&sns.DeleteTopicInput{
        TopicArn: r.TopicARN,
    })
    return err
}

func (r *SNSTopic) Properties() types.Properties {
    return types.NewPropertiesFromStruct(r)
}

func (r *SNSTopic) String() string {
    return fmt.Sprintf("TopicARN: %s", *r.TopicARN)
}

type SNSTopicLister struct{}

func (l *SNSTopicLister) List(_ context.Context, _ interface{}) ([]resource.Resource, error) {
    return nil, nil
}