ekristen/libnuke

View on GitHub
pkg/registry/registry.go

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
// Package registry provides a way to register resources and their listers and obtain them after the fact. The registry
// is currently deeply embedded with the other packages and how they access specific aspects of a resource.
package registry

import (
    "context"
    "fmt"

    "github.com/sirupsen/logrus"
    "github.com/stevenle/topsort"

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

// Scope is a string in which resources are grouped against, this is meant for upstream tools to define their
// own scopes if the DefaultScope is not sufficient. For example Azure has multiple levels of scoping for resources,
// whereas AWS does not.
type Scope string

// DefaultScope is the default scope which resources are registered against if no other scope is provided
const DefaultScope Scope = "default"

// Registrations is a map of resource type to registration
type Registrations map[string]*Registration

// Listers is a map of resource type to lister
type Listers map[string]Lister

// resourceListers is a global variable of all registered resource listers
var resourceListers = make(Listers)

// registrations is a global variable of all registrations for resources
var registrations = make(Registrations)

// alternatives is a global variable of all alternative resource types
var alternatives = make(map[string]string)

// graph is a global variable of the graph of resource dependencies
var graph = topsort.NewGraph()

// Registration is a struct that contains the information needed to register a resource lister
type Registration struct {
    // Name is the name of the resource type
    Name string

    // Scope is the scope of the resource type, if left empty it'll default to DefaultScope. It's simple a string
    // designed to group resource types together. The primary use case is for Azure, it needs resources scoped to
    // different levels, whereas AWS has simply Account level.
    Scope Scope

    // Resource is the resource type that the lister is going to list. This is a struct that implements the Resource
    // interface. This is primarily used to generate documentation by parsing the structs properties and generating
    // markdown documentation.
    // Note: it is a interface{} because we are going to inspect it, we do not need to actually call any methods on it.
    Resource interface{}

    // Lister is the lister for the resource type, it is a struct with a method called List that returns a slice
    // of resources. The lister is responsible for filtering out any resources that should not be deleted because they
    // are ineligible for deletion. For example, built in resources that cannot be deleted.
    Lister Lister

    // Settings allows for resources to define settings that can be configured by the calling tool to change the
    // behavior of the resource. For example, EC2 and RDS Instances have Deletion Protection, this allows the resource
    // to define a setting that can be configured by the calling tool to enable/disable deletion protection.
    Settings []string

    // DependsOn is a list of resource types that this resource type depends on. This is used to determine
    // the order in which resources are deleted. For example, a VPC depends on subnets, so we want to delete
    // the subnets before we delete the VPC. This is a Resource level dependency, not a resource instance (i.e. all
    // subnets must be deleted before any VPC can be deleted, not just the subnets that are associated with the VPC).
    DependsOn []string

    // DeprecatedAliases is a list of deprecated aliases for the resource type, usually misspellings or old names
    // that have been replaced with a new resource type. This is used to map the old resource type to the new
    // resource type. This is used in the config package to resolve any deprecated resource types and provide
    // notifications to the user.
    DeprecatedAliases []string

    // AlternativeResource is used to determine if there's an alternative resource type to use. The primary use case
    // for this is AWS Cloud Control API, where we want to use the Cloud Control API resource type instead of the
    // default resource type. However, any resource that uses a different API to manage the same resource can use this
    // field.
    AlternativeResource string
}

// Lister is an interface that represents a resource that can be listed
type Lister interface {
    List(ctx context.Context, opts interface{}) ([]resource.Resource, error)
}

// ListerWithClose is an interface that represents a lister that can be closed. Use Case: GCP clients need to be closed.
type ListerWithClose interface {
    Close()
}

// RegisterOption is a function that can be used to manipulate the lister for a given resource type at
// registration time
type RegisterOption func(name string, lister Lister)

// Register registers a resource lister with the registry
func Register(r *Registration) {
    if r.Scope == "" {
        r.Scope = DefaultScope
    }

    if _, exists := registrations[r.Name]; exists {
        panic(fmt.Sprintf("a resource with the name %s already exists", r.Name))
    }

    if r.AlternativeResource != "" {
        if _, exists := alternatives[r.AlternativeResource]; exists {
            panic(fmt.Sprintf("an alternative resource mapping for %s already exists", r.AlternativeResource))
        }

        alternatives[r.AlternativeResource] = r.Name
    }

    logrus.WithField("name", r.Name).Trace("registered resource lister")

    registrations[r.Name] = r
    resourceListers[r.Name] = r.Lister

    graph.AddNode(r.Name)
    if len(r.DependsOn) == 0 {
        // Note: AddEdge will never through an error
        _ = graph.AddEdge("root", r.Name)
    }
    for _, dep := range r.DependsOn {
        // Note: AddEdge will never through an error
        _ = graph.AddEdge(dep, r.Name)
    }
}

// GetRegistrations returns all registrations
func GetRegistrations() Registrations {
    return registrations
}

// ClearRegistry clears the registry of all registrations
// Designed for use for unit tests, not for production code. Only use if you know what you are doing.
func ClearRegistry() {
    registrations = make(Registrations)
    resourceListers = make(Listers)
    graph = topsort.NewGraph()
}

func GetListers() (listers Listers) {
    listers = make(Listers)
    for name, r := range registrations {
        listers[name] = r.Lister
    }
    return listers
}

// GetRegistration returns the registration for the given resource type
func GetRegistration(name string) *Registration {
    return registrations[name]
}

// GetListersV2 returns a map of listers based on graph top sort order
func GetListersV2() (listers Listers) {
    listers = make(Listers)
    sorted, err := graph.TopSort("root")
    if err != nil {
        panic(err)
    }
    for _, name := range sorted {
        if name == "root" {
            continue
        }
        r := registrations[name]
        listers[name] = r.Lister
    }

    return listers
}

// GetListersForScope returns a map of listers for a particular scope that they've been grouped by
func GetListersForScope(scope Scope) (listers Listers) {
    listers = make(Listers)
    for name, r := range registrations {
        if r.Scope == scope {
            listers[name] = r.Lister
        }
    }
    return listers
}

// GetNames provides a string slice of all lister names that have been registered
func GetNames() []string {
    var names []string
    for resourceType := range GetListersV2() {
        names = append(names, resourceType)
    }

    return names
}

// GetNamesForScope provides a string slice of all listers for a particular scope
func GetNamesForScope(scope Scope) []string {
    var names []string
    for resourceType := range GetListersForScope(scope) {
        names = append(names, resourceType)
    }
    return names
}

// GetLister gets a specific lister by name
func GetLister(name string) Lister {
    return resourceListers[name]
}

// GetAlternativeResourceTypeMapping returns a map of resource types to their alternative resource type. The primary
// use case is used to map resource types to their alternative AWS Cloud Control resource type. This allows each
// resource to define what it's alternative resource type is instead of trying to track them in a single place.
func GetAlternativeResourceTypeMapping() map[string]string {
    mapping := make(map[string]string)
    for _, r := range registrations {
        if r.AlternativeResource != "" {
            mapping[r.Name] = r.AlternativeResource
        }
    }
    return mapping
}

// GetDeprecatedResourceTypeMapping returns a map of deprecated resource types to their replacement. The primary use
// case is used to map deprecated resource types to their replacement in the config package. This allows us to
// provide notifications to the user that they are using a deprecated resource type and should update their config.
// This allow allows each resource to define it's DeprecatedAliases instead of trying to track them in a single place.
func GetDeprecatedResourceTypeMapping() map[string]string {
    mapping := make(map[string]string)
    for _, r := range registrations {
        for _, alias := range r.DeprecatedAliases {
            mapping[alias] = r.Name
        }
    }
    return mapping
}