205 lines
5.3 KiB
Go
205 lines
5.3 KiB
Go
package source
|
|
|
|
import (
|
|
portainer "github.com/portainer/portainer/api"
|
|
"github.com/portainer/portainer/api/dataservices"
|
|
gittypes "github.com/portainer/portainer/api/git/types"
|
|
)
|
|
|
|
type ServiceTx struct {
|
|
base dataservices.BaseDataServiceTx[portainer.Source, portainer.SourceID]
|
|
}
|
|
|
|
// Create creates a new source.
|
|
func (service ServiceTx) Create(context UserContext, source *portainer.Source) error {
|
|
if err := validateUserContext(context); err != nil {
|
|
return err
|
|
}
|
|
|
|
if source == nil {
|
|
return ErrInvalidSource
|
|
}
|
|
|
|
if err := sanitizeGitSource(source); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := sanitizeAccesses(context, source, nil); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := enforceUniqueGitSource(service, source); err != nil {
|
|
return err
|
|
}
|
|
|
|
return service.base.Tx.CreateObject(
|
|
BucketName,
|
|
func(id uint64) (int, any) {
|
|
source.ID = portainer.SourceID(id)
|
|
return int(source.ID), source
|
|
},
|
|
)
|
|
}
|
|
|
|
func (service ServiceTx) Read(context UserContext, ID portainer.SourceID) (*portainer.Source, error) {
|
|
if err := validateUserContext(context); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
source, err := service.base.Read(ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := enforceUserPermissions(context, source, actionRead); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return source, err
|
|
}
|
|
|
|
// Access is not enforced on this to avoid the cost of deserialize
|
|
// Any user can scan the DB IDs using this method, so be mindful with usage of this func.
|
|
func (service ServiceTx) Exists(context UserContext, ID portainer.SourceID) (bool, error) {
|
|
if err := validateUserContext(context); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return service.base.Exists(ID)
|
|
}
|
|
|
|
// ReadAll fetches all sources the user can access, matching predicates
|
|
func (service ServiceTx) ReadAll(context UserContext, predicates ...func(portainer.Source) bool) ([]portainer.Source, error) {
|
|
if err := validateUserContext(context); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
list, err := service.base.ReadAll(predicates...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return filterSources(list, context), nil
|
|
}
|
|
|
|
// Update updates the source of id `ID` with the `source` content
|
|
// It validates that the user has access to the source, and has enough permissions to perform the action
|
|
func (service ServiceTx) Update(context UserContext, ID portainer.SourceID, source *portainer.Source) error {
|
|
if err := validateUserContext(context); err != nil {
|
|
return err
|
|
}
|
|
|
|
originalSource, err := service.base.Read(ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if source == nil || originalSource == nil {
|
|
return ErrInvalidSource
|
|
}
|
|
|
|
if err := enforceUserPermissions(context, originalSource, actionWrite); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := sanitizeGitSource(source); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := sanitizeAccesses(context, source, originalSource); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := enforceUniqueGitSource(service, source); err != nil {
|
|
return err
|
|
}
|
|
|
|
return service.base.Update(ID, source)
|
|
}
|
|
|
|
// Delete deletes a source
|
|
// It validates that the user has access to the source, and has enough permissions to perform the action
|
|
func (service ServiceTx) Delete(context UserContext, ID portainer.SourceID) error {
|
|
if err := validateUserContext(context); err != nil {
|
|
return err
|
|
}
|
|
|
|
source, err := service.base.Read(ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := enforceUserPermissions(context, source, actionWrite); err != nil {
|
|
return err
|
|
}
|
|
|
|
return service.base.Delete(ID)
|
|
}
|
|
|
|
// FindOrCreateGitSource returns an existing Source whose URL and authentication match cfg,
|
|
// or creates a new one. Only URL, authentication, and TLSSkipVerify are stored on the Source;
|
|
// per-stack fields (ReferenceName, ConfigFilePath, ConfigHash) belong in the Artifact.
|
|
// The function auto adds the user to an existing source if the user doesn't have access but provided a valid full
|
|
// config (URL+Auth)
|
|
func (service ServiceTx) FindOrCreateGitSource(context UserContext, src *portainer.Source) (*portainer.Source, error) {
|
|
if err := validateUserContext(context); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if src == nil || src.Git == nil {
|
|
return nil, ErrInvalidSource
|
|
}
|
|
|
|
normalized, err := normalizeGitSource(src)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
existing, err := service.base.ReadAll(func(s portainer.Source) bool {
|
|
n, err := normalizeGitSource(&s)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return normalized.Equal(n)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(existing) > 0 {
|
|
allowed := filterSources(existing, context)
|
|
if len(allowed) > 0 {
|
|
return &allowed[0], nil
|
|
}
|
|
|
|
// give user access to the first source if he doesn't have access
|
|
// to any of the sources that have the same url+auth
|
|
existing[0].UserAccesses = append(existing[0].UserAccesses, context.ID())
|
|
if err := service.base.Update(existing[0].ID, &existing[0]); err != nil {
|
|
return nil, err
|
|
}
|
|
return &existing[0], nil
|
|
}
|
|
|
|
toCreate := &portainer.Source{
|
|
Name: src.Name,
|
|
Type: portainer.SourceTypeGit,
|
|
Git: &gittypes.GitSource{
|
|
URL: src.Git.URL,
|
|
Authentication: src.Git.Authentication,
|
|
TLSSkipVerify: src.Git.TLSSkipVerify,
|
|
},
|
|
Public: src.Public,
|
|
AdministratorsOnly: src.AdministratorsOnly,
|
|
UserAccesses: src.UserAccesses,
|
|
TeamAccesses: src.TeamAccesses,
|
|
OwnerID: src.OwnerID,
|
|
}
|
|
|
|
if err := service.Create(context, toCreate); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return toCreate, nil
|
|
}
|