package automation import ( "context" "fmt" "time" "github.com/openziti/edge-api/rest_management_api_client" "github.com/openziti/edge-api/rest_model" "github.com/pkg/errors" ) type IResourceManager[T any, CreateOpts any] interface { Create(opts CreateOpts) (string, error) Delete(id string) error Find(opts *FilterOptions) ([]*T, error) GetByID(id string) (*T, error) GetByName(name string) (*T, error) } type ErrorType int const ( ErrorTypeNotFound ErrorType = iota ErrorTypePermission ErrorTypeValidation ErrorTypeNetwork ErrorTypeInternal ) type AutomationError struct { Type ErrorType Resource string Operation string Cause error } func (e *AutomationError) Error() string { return fmt.Sprintf("%s operation failed on %s: %v", e.Operation, e.Resource, e.Cause) } func (e *AutomationError) IsNotFound() bool { return e.Type == ErrorTypeNotFound } func (e *AutomationError) IsRetryable() bool { return e.Type == ErrorTypeNetwork } func NewNotFoundError(resource, operation string, cause error) *AutomationError { return &AutomationError{ Type: ErrorTypeNotFound, Resource: resource, Operation: operation, Cause: cause, } } type BaseOptions struct { Name string Tags *Tags TagContext map[string]interface{} Timeout time.Duration } func (bo *BaseOptions) GetTimeout() time.Duration { if bo.Timeout == 0 { return DefaultRequestTimeout } return bo.Timeout } func (bo *BaseOptions) GetTags() *rest_model.Tags { if bo.Tags != nil { return bo.Tags.ToRestModel() } return &rest_model.Tags{SubTags: make(map[string]interface{})} } type BaseResourceManager[T any] struct { client *ZitiAutomation } func NewBaseResourceManager[T any](ziti *ZitiAutomation) *BaseResourceManager[T] { return &BaseResourceManager[T]{client: ziti} } func (brm *BaseResourceManager[T]) Edge() *rest_management_api_client.ZitiEdgeManagement { return brm.client.Edge() } func (brm *BaseResourceManager[T]) Context() context.Context { return context.Background() } // generic helper for GetByID operations func GetByID[T any](finder func(*FilterOptions) ([]*T, error), id string, resourceType string) (*T, error) { opts := &FilterOptions{Filter: BuildFilter("id", id)} items, err := finder(opts) if err != nil { return nil, errors.Wrapf(err, "error finding %s by id", resourceType) } if len(items) == 0 { return nil, NewNotFoundError(resourceType, "GetByID", errors.Errorf("%s with id '%s' not found", resourceType, id)) } if len(items) != 1 { return nil, errors.Errorf("expected 1 %s, found %d", resourceType, len(items)) } return items[0], nil } // generic helper for GetByName operations func GetByName[T any](finder func(*FilterOptions) ([]*T, error), name string, resourceType string) (*T, error) { opts := &FilterOptions{Filter: BuildFilter("name", name)} items, err := finder(opts) if err != nil { return nil, errors.Wrapf(err, "error finding %s by name", resourceType) } if len(items) == 0 { return nil, NewNotFoundError(resourceType, "GetByName", errors.Errorf("%s with name '%s' not found", resourceType, name)) } if len(items) != 1 { return nil, errors.Errorf("expected 1 %s with name '%s', found %d", resourceType, name, len(items)) } return items[0], nil } // generic helper for bulk delete operations func DeleteWithFilter[T any](finder func(*FilterOptions) ([]*T, error), deleter func(string) error, filter string, resourceType string) error { opts := &FilterOptions{Filter: filter} items, err := finder(opts) if err != nil { return errors.Wrapf(err, "error finding %s for deletion", resourceType) } for _, item := range items { id, err := getResourceID(item) if err != nil { return errors.Wrapf(err, "error extracting ID for %s", resourceType) } if err := deleter(id); err != nil { return errors.Wrapf(err, "error deleting %s '%s'", resourceType, id) } } return nil } // helper to extract ID from any resource type func getResourceID(item interface{}) (string, error) { switch v := item.(type) { case *rest_model.IdentityDetail: return *v.ID, nil case *rest_model.ServiceDetail: return *v.ID, nil case *rest_model.ConfigDetail: return *v.ID, nil case *rest_model.ConfigTypeDetail: return *v.ID, nil case *rest_model.EdgeRouterPolicyDetail: return *v.ID, nil case *rest_model.ServiceEdgeRouterPolicyDetail: return *v.ID, nil case *rest_model.ServicePolicyDetail: return *v.ID, nil default: return "", errors.Errorf("unsupported resource type: %T", item) } } type FilterOptions struct { Filter string Limit int64 Offset int64 Timeout time.Duration } func (fo *FilterOptions) GetTimeout() time.Duration { if fo.Timeout == 0 { return DefaultRequestTimeout } return fo.Timeout } func (fo *FilterOptions) GetLimit() int64 { if fo.Limit == 0 { return 100 } return fo.Limit } func BuildFilter(field, value string) string { return fmt.Sprintf("%s=\"%s\"", field, value) } func BuildTagFilter(tag, value string) string { return fmt.Sprintf("tags.%s=\"%s\"", tag, value) }