package stack import ( "errors" "fmt" "strings" portainer "github.com/portainer/portainer/api" dserrors "github.com/portainer/portainer/api/dataservices/errors" "github.com/rs/zerolog/log" ) const ( // BucketName represents the name of the bucket where this service stores data. BucketName = "stacks" ) // Service represents a service for managing environment(endpoint) data. type Service struct { connection portainer.Connection } func (service *Service) BucketName() string { return BucketName } // NewService creates a new instance of a service. func NewService(connection portainer.Connection) (*Service, error) { err := connection.SetServiceName(BucketName) if err != nil { return nil, err } return &Service{ connection: connection, }, nil } // Stack returns a stack object by ID. func (service *Service) Stack(ID portainer.StackID) (*portainer.Stack, error) { var stack portainer.Stack identifier := service.connection.ConvertToKey(int(ID)) err := service.connection.GetObject(BucketName, identifier, &stack) if err != nil { return nil, err } return &stack, nil } // StackByName returns a stack object by name. func (service *Service) StackByName(name string) (*portainer.Stack, error) { var s *portainer.Stack stop := fmt.Errorf("ok") err := service.connection.GetAll( BucketName, &portainer.Stack{}, func(obj interface{}) (interface{}, error) { stack, ok := obj.(*portainer.Stack) if !ok { log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object") return nil, fmt.Errorf("Failed to convert to Stack object: %s", obj) } if stack.Name == name { s = stack return nil, stop } return &portainer.Stack{}, nil }) if errors.Is(err, stop) { return s, nil } if err == nil { return nil, dserrors.ErrObjectNotFound } return nil, err } // Stacks returns an array containing all the stacks with same name func (service *Service) StacksByName(name string) ([]portainer.Stack, error) { var stacks = make([]portainer.Stack, 0) err := service.connection.GetAll( BucketName, &portainer.Stack{}, func(obj interface{}) (interface{}, error) { stack, ok := obj.(portainer.Stack) if !ok { log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object") return nil, fmt.Errorf("failed to convert to Stack object: %s", obj) } if stack.Name == name { stacks = append(stacks, stack) } return &portainer.Stack{}, nil }) return stacks, err } // Stacks returns an array containing all the stacks. func (service *Service) Stacks() ([]portainer.Stack, error) { var stacks = make([]portainer.Stack, 0) err := service.connection.GetAll( BucketName, &portainer.Stack{}, func(obj interface{}) (interface{}, error) { stack, ok := obj.(*portainer.Stack) if !ok { log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object") return nil, fmt.Errorf("Failed to convert to Stack object: %s", obj) } stacks = append(stacks, *stack) return &portainer.Stack{}, nil }) return stacks, err } // GetNextIdentifier returns the next identifier for a stack. func (service *Service) GetNextIdentifier() int { return service.connection.GetNextIdentifier(BucketName) } // CreateStack creates a new stack. func (service *Service) Create(stack *portainer.Stack) error { return service.connection.CreateObjectWithId(BucketName, int(stack.ID), stack) } // UpdateStack updates a stack. func (service *Service) UpdateStack(ID portainer.StackID, stack *portainer.Stack) error { identifier := service.connection.ConvertToKey(int(ID)) return service.connection.UpdateObject(BucketName, identifier, stack) } // DeleteStack deletes a stack. func (service *Service) DeleteStack(ID portainer.StackID) error { identifier := service.connection.ConvertToKey(int(ID)) return service.connection.DeleteObject(BucketName, identifier) } // StackByWebhookID returns a pointer to a stack object by webhook ID. // It returns nil, errors.ErrObjectNotFound if there's no stack associated with the webhook ID. func (service *Service) StackByWebhookID(id string) (*portainer.Stack, error) { var s *portainer.Stack stop := fmt.Errorf("ok") err := service.connection.GetAll( BucketName, &portainer.Stack{}, func(obj interface{}) (interface{}, error) { var ok bool s, ok = obj.(*portainer.Stack) if !ok { log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object") return &portainer.Stack{}, nil } if s.AutoUpdate != nil && strings.EqualFold(s.AutoUpdate.Webhook, id) { return nil, stop } return &portainer.Stack{}, nil }) if errors.Is(err, stop) { return s, nil } if err == nil { return nil, dserrors.ErrObjectNotFound } return nil, err } // RefreshableStacks returns stacks that are configured for a periodic update func (service *Service) RefreshableStacks() ([]portainer.Stack, error) { stacks := make([]portainer.Stack, 0) err := service.connection.GetAll( BucketName, &portainer.Stack{}, func(obj interface{}) (interface{}, error) { stack, ok := obj.(*portainer.Stack) if !ok { log.Debug().Str("obj", fmt.Sprintf("%#v", obj)).Msg("failed to convert to Stack object") return nil, fmt.Errorf("Failed to convert to Stack object: %s", obj) } if stack.AutoUpdate != nil && stack.AutoUpdate.Interval != "" { stacks = append(stacks, *stack) } return &portainer.Stack{}, nil }) return stacks, err }