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 inProperties()
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(®istry.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(®istry.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
}