docs/resources.md
# 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.
```go
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.
```go
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
```go
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.
```bash
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:
```bash
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**
```go
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**
```go
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
}
```