feat(app/sources): UAC on sources (#2997)
Co-authored-by: Chaim Lev-Ari <chaim.lev-ari@portainer.io> Co-authored-by: andres-portainer <91705312+andres-portainer@users.noreply.github.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/dataservices/source"
|
||||
gittypes "github.com/portainer/portainer/api/git/types"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||
@@ -22,6 +23,8 @@ func FetchWorkflows(
|
||||
) ([]Workflow, error) {
|
||||
gitConfigs := map[portainer.StackID]*gittypes.RepoConfig{}
|
||||
|
||||
userContext := source.NewUserContext(sc.User, sc.UserMemberships)
|
||||
|
||||
stacks, err := tx.Stack().ReadAll(func(s portainer.Stack) bool {
|
||||
return s.WorkflowID != 0 && (len(endpointIDSet) == 0 || endpointIDSet.Contains(s.EndpointID))
|
||||
})
|
||||
@@ -50,7 +53,7 @@ func FetchWorkflows(
|
||||
workflowIDSet.Add(stack.WorkflowID)
|
||||
}
|
||||
|
||||
workflowMap, sourceMap, err := LoadWorkflowAndSourceMaps(tx, workflowIDSet)
|
||||
workflowMap, sourceMap, err := LoadWorkflowAndSourceMaps(tx, userContext, workflowIDSet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -113,7 +116,9 @@ func FetchSourceStats(
|
||||
k8sFactory *cli.ClientFactory,
|
||||
sc *security.RestrictedRequestContext,
|
||||
) ([]portainer.Source, map[portainer.SourceID]SourceStats, error) {
|
||||
sources, err := tx.Source().ReadAll()
|
||||
userContext := source.NewUserContext(sc.User, sc.UserMemberships)
|
||||
|
||||
sources, err := tx.Source().ReadAll(userContext)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -15,7 +15,11 @@ import (
|
||||
)
|
||||
|
||||
func adminContext() *security.RestrictedRequestContext {
|
||||
return &security.RestrictedRequestContext{IsAdmin: true, UserID: 1}
|
||||
return &security.RestrictedRequestContext{
|
||||
IsAdmin: true,
|
||||
UserID: 1,
|
||||
User: &portainer.User{ID: 1, Role: portainer.AdministratorRole},
|
||||
}
|
||||
}
|
||||
|
||||
func mustCreateGitWorkflow(t *testing.T, tx dataservices.DataStoreTx, stack *portainer.Stack) {
|
||||
@@ -24,7 +28,7 @@ func mustCreateGitWorkflow(t *testing.T, tx dataservices.DataStoreTx, stack *por
|
||||
cfg := stack.GitConfig
|
||||
|
||||
src := &portainer.Source{Type: portainer.SourceTypeGit, Git: cfg}
|
||||
require.NoError(t, tx.Source().Create(src))
|
||||
require.NoError(t, tx.Source().Create(adminUserContext, src))
|
||||
|
||||
wf := &portainer.Workflow{Artifacts: []portainer.Artifact{{
|
||||
StackID: stack.ID,
|
||||
@@ -199,8 +203,8 @@ func TestFetchSourceStats_ReturnsAllSources(t *testing.T) {
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
require.NoError(t, tx.Source().Create(&portainer.Source{Name: "source-1", Type: portainer.SourceTypeGit}))
|
||||
require.NoError(t, tx.Source().Create(&portainer.Source{Name: "source-2", Type: portainer.SourceTypeGit}))
|
||||
require.NoError(t, tx.Source().Create(adminUserContext, &portainer.Source{Name: "source-1", Type: portainer.SourceTypeGit, Git: &gittypes.RepoConfig{URL: "http://github.com/org/repo1"}}))
|
||||
require.NoError(t, tx.Source().Create(adminUserContext, &portainer.Source{Name: "source-2", Type: portainer.SourceTypeGit, Git: &gittypes.RepoConfig{URL: "http://github.com/org/repo2"}}))
|
||||
|
||||
return tx.User().Create(&portainer.User{ID: 1, Role: portainer.AdministratorRole})
|
||||
}))
|
||||
@@ -223,8 +227,8 @@ func TestFetchSourceStats_TracksWorkflowCountAndEndpoints(t *testing.T) {
|
||||
var srcID portainer.SourceID
|
||||
|
||||
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{Name: "shared", Type: portainer.SourceTypeGit}
|
||||
require.NoError(t, tx.Source().Create(src))
|
||||
src := &portainer.Source{Name: "shared", Type: portainer.SourceTypeGit, Git: &gittypes.RepoConfig{URL: "http://github.com/org/repo"}}
|
||||
require.NoError(t, tx.Source().Create(adminUserContext, src))
|
||||
srcID = src.ID
|
||||
|
||||
for i := 1; i <= 2; i++ {
|
||||
@@ -261,8 +265,8 @@ func TestFetchSourceStats_UnusedSourceHasZeroStats(t *testing.T) {
|
||||
var unusedID portainer.SourceID
|
||||
|
||||
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{Name: "unused", Type: portainer.SourceTypeGit}
|
||||
require.NoError(t, tx.Source().Create(src))
|
||||
src := &portainer.Source{Name: "unused", Type: portainer.SourceTypeGit, Git: &gittypes.RepoConfig{URL: "http://github.com/org/repo"}}
|
||||
require.NoError(t, tx.Source().Create(adminUserContext, src))
|
||||
unusedID = src.ID
|
||||
|
||||
return tx.User().Create(&portainer.User{ID: 1, Role: portainer.AdministratorRole})
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package workflows
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer/api/dataservices/source"
|
||||
)
|
||||
|
||||
var adminUserContext = source.InsecureNewAdminContext()
|
||||
@@ -20,7 +20,7 @@ type gitSourceStore interface {
|
||||
// from the workflow identified by workflowID.
|
||||
// Source carries the shared fields (URL, auth, TLS); ArtifactFile carries the file-specific fields (ref, path, hash).
|
||||
// Returns nil, nil, nil when workflowID is 0 or no matching entry is found.
|
||||
func GitSourceAndArtifactForStack(tx gitSourceStore, workflowID portainer.WorkflowID, stackID portainer.StackID) (*portainer.Source, *portainer.ArtifactFile, error) {
|
||||
func GitSourceAndArtifactForStack(tx gitSourceStore, userContext *dataservices.SourceServiceUserContext, workflowID portainer.WorkflowID, stackID portainer.StackID) (*portainer.Source, *portainer.ArtifactFile, error) {
|
||||
if workflowID == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func GitSourceAndArtifactForStack(tx gitSourceStore, workflowID portainer.Workfl
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sourceMap, err := loadWorkflowSources(tx, wf)
|
||||
sourceMap, err := loadWorkflowSources(tx, userContext, wf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func GitSourceAndArtifactForStack(tx gitSourceStore, workflowID portainer.Workfl
|
||||
|
||||
// GitSourceAndArtifactForEdgeStack returns the git Source and the ArtifactFile matching edgeStackID.
|
||||
// Returns nil, nil, nil when workflowID is 0 or no matching entry is found.
|
||||
func GitSourceAndArtifactForEdgeStack(tx gitSourceStore, workflowID portainer.WorkflowID, edgeStackID portainer.EdgeStackID) (*portainer.Source, *portainer.ArtifactFile, error) {
|
||||
func GitSourceAndArtifactForEdgeStack(tx gitSourceStore, userContext *dataservices.SourceServiceUserContext, workflowID portainer.WorkflowID, edgeStackID portainer.EdgeStackID) (*portainer.Source, *portainer.ArtifactFile, error) {
|
||||
if workflowID == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
@@ -67,7 +67,7 @@ func GitSourceAndArtifactForEdgeStack(tx gitSourceStore, workflowID portainer.Wo
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sourceMap, err := loadWorkflowSources(tx, wf)
|
||||
sourceMap, err := loadWorkflowSources(tx, userContext, wf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -169,45 +169,15 @@ func UpdateArtifactFileForEdgeStack(tx gitSourceStore, workflowID portainer.Work
|
||||
// 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.
|
||||
func FindOrCreateGitSource(tx gitSourceStore, src *portainer.Source) (*portainer.Source, error) {
|
||||
src.Git.URL = gittypes.SanitizeURL(src.Git.URL)
|
||||
|
||||
existing, err := tx.Source().ReadAll(func(s portainer.Source) bool {
|
||||
return s.Type == portainer.SourceTypeGit &&
|
||||
s.Git != nil &&
|
||||
s.Git.URL == src.Git.URL &&
|
||||
gitAuthMatches(s.Git.Authentication, src.Git.Authentication)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(existing) > 0 {
|
||||
return &existing[0], nil
|
||||
}
|
||||
|
||||
toCreate := &portainer.Source{
|
||||
Name: src.Name,
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
URL: src.Git.URL,
|
||||
Authentication: src.Git.Authentication,
|
||||
TLSSkipVerify: src.Git.TLSSkipVerify,
|
||||
},
|
||||
}
|
||||
|
||||
if err := tx.Source().Create(toCreate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toCreate, nil
|
||||
func FindOrCreateGitSource(tx gitSourceStore, userContext *dataservices.SourceServiceUserContext, src *portainer.Source) (*portainer.Source, error) {
|
||||
return tx.Source().FindOrCreateGitSource(userContext, src)
|
||||
}
|
||||
|
||||
// SaveWorkflowGitConfig persists URL/auth/TLS on the Source and ref/path/hash on the Artifact
|
||||
// matched by matchArtifact. When the URL changes, an existing or new Source is located via
|
||||
// FindOrCreateGitSource and the Workflow's SourceID is updated atomically alongside the Artifact fields.
|
||||
func SaveWorkflowGitConfig(tx gitSourceStore, workflowID portainer.WorkflowID, matchArtifact func(portainer.Artifact) bool, oldSourceID portainer.SourceID, cfg *gittypes.RepoConfig) error {
|
||||
src, err := tx.Source().Read(oldSourceID)
|
||||
func SaveWorkflowGitConfig(tx gitSourceStore, userContext *dataservices.SourceServiceUserContext, workflowID portainer.WorkflowID, matchArtifact func(portainer.Artifact) bool, oldSourceID portainer.SourceID, cfg *gittypes.RepoConfig) error {
|
||||
src, err := tx.Source().Read(userContext, oldSourceID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read source: %w", err)
|
||||
}
|
||||
@@ -219,7 +189,7 @@ func SaveWorkflowGitConfig(tx gitSourceStore, workflowID portainer.WorkflowID, m
|
||||
newSourceID := oldSourceID
|
||||
|
||||
if cfg.URL != src.Git.URL {
|
||||
newSrc, err := FindOrCreateGitSource(tx, &portainer.Source{
|
||||
newSrc, err := FindOrCreateGitSource(tx, userContext, &portainer.Source{
|
||||
Name: gittypes.RepoName(cfg.URL),
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: cfg,
|
||||
@@ -233,7 +203,7 @@ func SaveWorkflowGitConfig(tx gitSourceStore, workflowID portainer.WorkflowID, m
|
||||
src.Git.Authentication = cfg.Authentication
|
||||
src.Git.TLSSkipVerify = cfg.TLSSkipVerify
|
||||
|
||||
if err := tx.Source().Update(src.ID, src); err != nil {
|
||||
if err := tx.Source().Update(userContext, src.ID, src); err != nil {
|
||||
return fmt.Errorf("failed to update source: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -297,7 +267,7 @@ func LoadWorkflowMap(tx gitSourceStore, ids set.Set[portainer.WorkflowID]) (map[
|
||||
|
||||
// LoadWorkflowAndSourceMaps fetches workflows by their IDs and the sources they reference,
|
||||
// collecting source IDs in a single pass over the workflows.
|
||||
func LoadWorkflowAndSourceMaps(tx gitSourceStore, ids set.Set[portainer.WorkflowID]) (map[portainer.WorkflowID]portainer.Workflow, map[portainer.SourceID]portainer.Source, error) {
|
||||
func LoadWorkflowAndSourceMaps(tx gitSourceStore, userContext *dataservices.SourceServiceUserContext, ids set.Set[portainer.WorkflowID]) (map[portainer.WorkflowID]portainer.Workflow, map[portainer.SourceID]portainer.Source, error) {
|
||||
wfMap := make(map[portainer.WorkflowID]portainer.Workflow, len(ids))
|
||||
sourceIDs := make(set.Set[portainer.SourceID])
|
||||
for id := range ids {
|
||||
@@ -313,7 +283,7 @@ func LoadWorkflowAndSourceMaps(tx gitSourceStore, ids set.Set[portainer.Workflow
|
||||
}
|
||||
}
|
||||
|
||||
srcMap, err := LoadSourceMap(tx, sourceIDs)
|
||||
srcMap, err := loadSourceMap(tx, userContext, sourceIDs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -323,7 +293,7 @@ func LoadWorkflowAndSourceMaps(tx gitSourceStore, ids set.Set[portainer.Workflow
|
||||
|
||||
// loadWorkflowSources collects all unique SourceIDs referenced by wf and returns them as a map.
|
||||
// This avoids reading the same Source record more than once when files share a SourceID.
|
||||
func loadWorkflowSources(tx gitSourceStore, wf *portainer.Workflow) (map[portainer.SourceID]portainer.Source, error) {
|
||||
func loadWorkflowSources(tx gitSourceStore, userContext *dataservices.SourceServiceUserContext, wf *portainer.Workflow) (map[portainer.SourceID]portainer.Source, error) {
|
||||
ids := make(set.Set[portainer.SourceID])
|
||||
for _, as := range wf.Artifacts {
|
||||
for _, f := range as.Files {
|
||||
@@ -331,67 +301,22 @@ func loadWorkflowSources(tx gitSourceStore, wf *portainer.Workflow) (map[portain
|
||||
}
|
||||
}
|
||||
|
||||
return LoadSourceMap(tx, ids)
|
||||
return loadSourceMap(tx, userContext, ids)
|
||||
}
|
||||
|
||||
// LoadSourceMap fetches sources by their IDs and returns them keyed by ID.
|
||||
func LoadSourceMap(tx gitSourceStore, ids set.Set[portainer.SourceID]) (map[portainer.SourceID]portainer.Source, error) {
|
||||
// loadSourceMap fetches sources by their IDs and returns them keyed by ID.
|
||||
func loadSourceMap(tx gitSourceStore, userContext *dataservices.SourceServiceUserContext, ids set.Set[portainer.SourceID]) (map[portainer.SourceID]portainer.Source, error) {
|
||||
sources, err := tx.Source().ReadAll(userContext, func(s portainer.Source) bool {
|
||||
return ids.Contains(s.ID)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[portainer.SourceID]portainer.Source, len(ids))
|
||||
for id := range ids {
|
||||
src, err := tx.Source().Read(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[id] = *src
|
||||
for _, src := range sources {
|
||||
result[src.ID] = src
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func gitAuthMatches(a, b *gittypes.GitAuthentication) bool {
|
||||
if a == nil && b == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return a.Username == b.Username && a.Password == b.Password
|
||||
}
|
||||
|
||||
// ValidateUniqueSource validates there are no other sources with the same URL and credentials.
|
||||
// Pass empty strings for username and password when the source has no authentication.
|
||||
func ValidateUniqueSource(tx gitSourceStore, url, username, password string, sourceID portainer.SourceID) (bool, error) {
|
||||
normalizedURL, err := gittypes.NormalizeURL(gittypes.SanitizeURL(url))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
existing, err := tx.Source().ReadAll(func(s portainer.Source) bool {
|
||||
if s.ID == sourceID || s.Type != portainer.SourceTypeGit || s.Git == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
normalized, err := gittypes.NormalizeURL(gittypes.SanitizeURL(s.Git.URL))
|
||||
if err != nil || normalized != normalizedURL {
|
||||
return false
|
||||
}
|
||||
|
||||
existingUsername, existingPassword := gitAuthCredentials(s.Git.Authentication)
|
||||
return existingUsername == username && existingPassword == password
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(existing) == 0, nil
|
||||
}
|
||||
|
||||
func gitAuthCredentials(auth *gittypes.GitAuthentication) (username, password string) {
|
||||
if auth == nil {
|
||||
return "", ""
|
||||
}
|
||||
return auth.Username, auth.Password
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func TestGitSourceAndArtifactForStack_ZeroWorkflowIDReturnsNil(t *testing.T) {
|
||||
var file *portainer.ArtifactFile
|
||||
err := store.ViewTx(func(tx dataservices.DataStoreTx) error {
|
||||
var txErr error
|
||||
src, file, txErr = GitSourceAndArtifactForStack(tx, 0, 1)
|
||||
src, file, txErr = GitSourceAndArtifactForStack(tx, adminUserContext, 0, 1)
|
||||
return txErr
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -98,7 +98,7 @@ func TestGitSourceAndArtifactForStack_ReturnsMatchingSourceAndFile(t *testing.T)
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/repo"},
|
||||
}
|
||||
err := tx.Source().Create(gitSrc)
|
||||
err := tx.Source().Create(adminUserContext, gitSrc)
|
||||
require.NoError(t, err)
|
||||
|
||||
wf := &portainer.Workflow{
|
||||
@@ -124,7 +124,7 @@ func TestGitSourceAndArtifactForStack_ReturnsMatchingSourceAndFile(t *testing.T)
|
||||
var file *portainer.ArtifactFile
|
||||
err = store.ViewTx(func(tx dataservices.DataStoreTx) error {
|
||||
var txErr error
|
||||
src, file, txErr = GitSourceAndArtifactForStack(tx, workflowID, 42)
|
||||
src, file, txErr = GitSourceAndArtifactForStack(tx, adminUserContext, workflowID, 42)
|
||||
return txErr
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -146,7 +146,7 @@ func TestGitSourceAndArtifactForStack_NoMatchingArtifactReturnsNil(t *testing.T)
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/repo"},
|
||||
}
|
||||
err := tx.Source().Create(src)
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
|
||||
wf := &portainer.Workflow{
|
||||
@@ -167,43 +167,7 @@ func TestGitSourceAndArtifactForStack_NoMatchingArtifactReturnsNil(t *testing.T)
|
||||
var file *portainer.ArtifactFile
|
||||
err = store.ViewTx(func(tx dataservices.DataStoreTx) error {
|
||||
var txErr error
|
||||
src, file, txErr = GitSourceAndArtifactForStack(tx, workflowID, 99)
|
||||
return txErr
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, src)
|
||||
require.Nil(t, file)
|
||||
}
|
||||
|
||||
func TestGitSourceAndArtifactForStack_NonGitSourceSkipped(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
var workflowID portainer.WorkflowID
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
nonGitSrc := &portainer.Source{Type: portainer.SourceType(99)}
|
||||
err := tx.Source().Create(nonGitSrc)
|
||||
require.NoError(t, err)
|
||||
|
||||
wf := &portainer.Workflow{
|
||||
Artifacts: []portainer.Artifact{{
|
||||
StackID: 1,
|
||||
Files: []portainer.ArtifactFile{{SourceID: nonGitSrc.ID}},
|
||||
}},
|
||||
}
|
||||
err = tx.Workflow().Create(wf)
|
||||
require.NoError(t, err)
|
||||
workflowID = wf.ID
|
||||
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var src *portainer.Source
|
||||
var file *portainer.ArtifactFile
|
||||
err = store.ViewTx(func(tx dataservices.DataStoreTx) error {
|
||||
var txErr error
|
||||
src, file, txErr = GitSourceAndArtifactForStack(tx, workflowID, 1)
|
||||
src, file, txErr = GitSourceAndArtifactForStack(tx, adminUserContext, workflowID, 99)
|
||||
return txErr
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -219,7 +183,7 @@ func TestGitSourceAndArtifactForEdgeStack_ZeroWorkflowIDReturnsNil(t *testing.T)
|
||||
var file *portainer.ArtifactFile
|
||||
err := store.ViewTx(func(tx dataservices.DataStoreTx) error {
|
||||
var txErr error
|
||||
src, file, txErr = GitSourceAndArtifactForEdgeStack(tx, 0, 1)
|
||||
src, file, txErr = GitSourceAndArtifactForEdgeStack(tx, adminUserContext, 0, 1)
|
||||
return txErr
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -237,7 +201,7 @@ func TestGitSourceAndArtifactForEdgeStack_ReturnsMatchingSourceAndFile(t *testin
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/edge-repo"},
|
||||
}
|
||||
err := tx.Source().Create(gitSrc)
|
||||
err := tx.Source().Create(adminUserContext, gitSrc)
|
||||
require.NoError(t, err)
|
||||
|
||||
wf := &portainer.Workflow{
|
||||
@@ -262,7 +226,7 @@ func TestGitSourceAndArtifactForEdgeStack_ReturnsMatchingSourceAndFile(t *testin
|
||||
var file *portainer.ArtifactFile
|
||||
err = store.ViewTx(func(tx dataservices.DataStoreTx) error {
|
||||
var txErr error
|
||||
src, file, txErr = GitSourceAndArtifactForEdgeStack(tx, workflowID, 5)
|
||||
src, file, txErr = GitSourceAndArtifactForEdgeStack(tx, adminUserContext, workflowID, 5)
|
||||
return txErr
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -280,7 +244,7 @@ func TestUpdateArtifactFileForStack_NoMatchingArtifactIsNoOp(t *testing.T) {
|
||||
var sourceID portainer.SourceID
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{Type: portainer.SourceTypeGit, Git: &gittypes.RepoConfig{URL: "https://example.com"}}
|
||||
err := tx.Source().Create(src)
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
sourceID = src.ID
|
||||
|
||||
@@ -318,7 +282,7 @@ func TestUpdateArtifactFileForStack_AppliesFnAndPersists(t *testing.T) {
|
||||
var sourceID portainer.SourceID
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{Type: portainer.SourceTypeGit, Git: &gittypes.RepoConfig{URL: "https://example.com"}}
|
||||
err := tx.Source().Create(src)
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
sourceID = src.ID
|
||||
|
||||
@@ -356,7 +320,7 @@ func TestUpdateArtifactFileForEdgeStack_AppliesFnAndPersists(t *testing.T) {
|
||||
var sourceID portainer.SourceID
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{Type: portainer.SourceTypeGit, Git: &gittypes.RepoConfig{URL: "https://example.com"}}
|
||||
err := tx.Source().Create(src)
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
sourceID = src.ID
|
||||
|
||||
@@ -393,7 +357,7 @@ func TestFindOrCreateGitSource_CreatesNewSource(t *testing.T) {
|
||||
var src *portainer.Source
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
var txErr error
|
||||
src, txErr = FindOrCreateGitSource(tx, &portainer.Source{
|
||||
src, txErr = FindOrCreateGitSource(tx, adminUserContext, &portainer.Source{
|
||||
Name: "my-repo",
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
@@ -413,7 +377,7 @@ func TestFindOrCreateGitSource_ReusesExistingSourceForSameURLAndAuth(t *testing.
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
makeSource := func(tx dataservices.DataStoreTx) (*portainer.Source, error) {
|
||||
return FindOrCreateGitSource(tx, &portainer.Source{
|
||||
return FindOrCreateGitSource(tx, adminUserContext, &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
URL: "https://github.com/example/repo",
|
||||
@@ -446,7 +410,7 @@ func TestFindOrCreateGitSource_ReusesExistingSourceForSameURLAndAuth(t *testing.
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, firstID, secondID)
|
||||
|
||||
sources, err := store.Source().ReadAll()
|
||||
sources, err := store.Source().ReadAll(adminUserContext)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, sources, 1)
|
||||
}
|
||||
@@ -456,7 +420,7 @@ func TestFindOrCreateGitSource_DifferentAuthCreatesNewSource(t *testing.T) {
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
_, txErr := FindOrCreateGitSource(tx, &portainer.Source{
|
||||
_, txErr := FindOrCreateGitSource(tx, adminUserContext, &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
URL: "https://github.com/example/repo",
|
||||
@@ -468,7 +432,7 @@ func TestFindOrCreateGitSource_DifferentAuthCreatesNewSource(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
_, txErr := FindOrCreateGitSource(tx, &portainer.Source{
|
||||
_, txErr := FindOrCreateGitSource(tx, adminUserContext, &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
URL: "https://github.com/example/repo",
|
||||
@@ -479,7 +443,7 @@ func TestFindOrCreateGitSource_DifferentAuthCreatesNewSource(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
sources, err := store.Source().ReadAll()
|
||||
sources, err := store.Source().ReadAll(adminUserContext)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, sources, 2)
|
||||
}
|
||||
@@ -503,7 +467,7 @@ func TestSaveWorkflowGitConfig_UpdatesFileAndSourceWhenURLUnchanged(t *testing.T
|
||||
},
|
||||
},
|
||||
}
|
||||
err := tx.Source().Create(src)
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
sourceID = src.ID
|
||||
|
||||
@@ -539,7 +503,7 @@ func TestSaveWorkflowGitConfig_UpdatesFileAndSourceWhenURLUnchanged(t *testing.T
|
||||
}
|
||||
|
||||
err = store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
return SaveWorkflowGitConfig(tx, workflowID, func(a portainer.Artifact) bool {
|
||||
return SaveWorkflowGitConfig(tx, adminUserContext, workflowID, func(a portainer.Artifact) bool {
|
||||
return a.StackID == 1
|
||||
}, sourceID, newCfg)
|
||||
})
|
||||
@@ -552,7 +516,7 @@ func TestSaveWorkflowGitConfig_UpdatesFileAndSourceWhenURLUnchanged(t *testing.T
|
||||
require.Equal(t, "new-hash", wf.Artifacts[0].Files[0].Hash)
|
||||
require.Equal(t, sourceID, wf.Artifacts[0].Files[0].SourceID)
|
||||
|
||||
src, err := store.Source().Read(sourceID)
|
||||
src, err := store.Source().Read(adminUserContext, sourceID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "new-user", src.Git.Authentication.Username)
|
||||
require.Equal(t, "new-pass", src.Git.Authentication.Password)
|
||||
@@ -571,7 +535,7 @@ func TestSaveWorkflowGitConfig_CreatesNewSourceOnURLChange(t *testing.T) {
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/old-repo"},
|
||||
}
|
||||
err := tx.Source().Create(src)
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
oldSourceID = src.ID
|
||||
|
||||
@@ -592,7 +556,7 @@ func TestSaveWorkflowGitConfig_CreatesNewSourceOnURLChange(t *testing.T) {
|
||||
newCfg := &gittypes.RepoConfig{URL: "https://github.com/example/new-repo"}
|
||||
|
||||
err = store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
return SaveWorkflowGitConfig(tx, workflowID, func(a portainer.Artifact) bool {
|
||||
return SaveWorkflowGitConfig(tx, adminUserContext, workflowID, func(a portainer.Artifact) bool {
|
||||
return a.StackID == 1
|
||||
}, oldSourceID, newCfg)
|
||||
})
|
||||
@@ -603,7 +567,7 @@ func TestSaveWorkflowGitConfig_CreatesNewSourceOnURLChange(t *testing.T) {
|
||||
newSourceID := wf.Artifacts[0].Files[0].SourceID
|
||||
require.NotEqual(t, oldSourceID, newSourceID)
|
||||
|
||||
newSrc, err := store.Source().Read(newSourceID)
|
||||
newSrc, err := store.Source().Read(adminUserContext, newSourceID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "https://github.com/example/new-repo", newSrc.Git.URL)
|
||||
}
|
||||
@@ -620,7 +584,7 @@ func TestSaveWorkflowGitConfig_ReusesExistingSourceOnURLChange(t *testing.T) {
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/old-repo"},
|
||||
}
|
||||
err := tx.Source().Create(old)
|
||||
err := tx.Source().Create(adminUserContext, old)
|
||||
require.NoError(t, err)
|
||||
oldSourceID = old.ID
|
||||
|
||||
@@ -628,7 +592,7 @@ func TestSaveWorkflowGitConfig_ReusesExistingSourceOnURLChange(t *testing.T) {
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/shared-repo"},
|
||||
}
|
||||
err = tx.Source().Create(existing)
|
||||
err = tx.Source().Create(adminUserContext, existing)
|
||||
require.NoError(t, err)
|
||||
existingSourceID = existing.ID
|
||||
|
||||
@@ -649,7 +613,7 @@ func TestSaveWorkflowGitConfig_ReusesExistingSourceOnURLChange(t *testing.T) {
|
||||
newCfg := &gittypes.RepoConfig{URL: "https://github.com/example/shared-repo"}
|
||||
|
||||
err = store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
return SaveWorkflowGitConfig(tx, workflowID, func(a portainer.Artifact) bool {
|
||||
return SaveWorkflowGitConfig(tx, adminUserContext, workflowID, func(a portainer.Artifact) bool {
|
||||
return a.StackID == 1
|
||||
}, oldSourceID, newCfg)
|
||||
})
|
||||
@@ -659,46 +623,11 @@ func TestSaveWorkflowGitConfig_ReusesExistingSourceOnURLChange(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, existingSourceID, wf.Artifacts[0].Files[0].SourceID)
|
||||
|
||||
sources, err := store.Source().ReadAll()
|
||||
sources, err := store.Source().ReadAll(adminUserContext)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, sources, 2)
|
||||
}
|
||||
|
||||
func TestSaveWorkflowGitConfig_NilGitConfigReturnsError(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
var workflowID portainer.WorkflowID
|
||||
var sourceID portainer.SourceID
|
||||
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{Type: portainer.SourceTypeGit}
|
||||
err := tx.Source().Create(src)
|
||||
require.NoError(t, err)
|
||||
sourceID = src.ID
|
||||
|
||||
wf := &portainer.Workflow{
|
||||
Artifacts: []portainer.Artifact{{
|
||||
StackID: 1,
|
||||
Files: []portainer.ArtifactFile{{SourceID: sourceID}},
|
||||
}},
|
||||
}
|
||||
err = tx.Workflow().Create(wf)
|
||||
require.NoError(t, err)
|
||||
workflowID = wf.ID
|
||||
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
return SaveWorkflowGitConfig(tx, workflowID, func(a portainer.Artifact) bool {
|
||||
return a.StackID == 1
|
||||
}, sourceID, &gittypes.RepoConfig{URL: "https://github.com/example/repo"})
|
||||
})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSaveWorkflowGitConfig_OnlyMatchingArtifactUpdated(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
@@ -711,7 +640,7 @@ func TestSaveWorkflowGitConfig_OnlyMatchingArtifactUpdated(t *testing.T) {
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/repo"},
|
||||
}
|
||||
err := tx.Source().Create(src)
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
sourceID = src.ID
|
||||
|
||||
@@ -736,7 +665,7 @@ func TestSaveWorkflowGitConfig_OnlyMatchingArtifactUpdated(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
return SaveWorkflowGitConfig(tx, workflowID, func(a portainer.Artifact) bool {
|
||||
return SaveWorkflowGitConfig(tx, adminUserContext, workflowID, func(a portainer.Artifact) bool {
|
||||
return a.StackID == 1
|
||||
}, sourceID, &gittypes.RepoConfig{
|
||||
URL: "https://github.com/example/repo",
|
||||
@@ -759,7 +688,7 @@ func TestUpdateArtifactFileForStack_MultipleArtifactsOnlyMatchingUpdated(t *test
|
||||
var srcID portainer.SourceID
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{Type: portainer.SourceTypeGit, Git: &gittypes.RepoConfig{URL: "https://example.com"}}
|
||||
err := tx.Source().Create(src)
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
srcID = src.ID
|
||||
|
||||
@@ -804,7 +733,7 @@ func TestSaveWorkflowArtifact_SwitchesSourceWithoutMutatingIt(t *testing.T) {
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/repo"},
|
||||
}
|
||||
err := tx.Source().Create(old)
|
||||
err := tx.Source().Create(adminUserContext, old)
|
||||
require.NoError(t, err)
|
||||
oldSourceID = old.ID
|
||||
|
||||
@@ -818,7 +747,7 @@ func TestSaveWorkflowArtifact_SwitchesSourceWithoutMutatingIt(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
err = tx.Source().Create(selected)
|
||||
err = tx.Source().Create(adminUserContext, selected)
|
||||
require.NoError(t, err)
|
||||
newSourceID = selected.ID
|
||||
|
||||
@@ -861,7 +790,7 @@ func TestSaveWorkflowArtifact_SwitchesSourceWithoutMutatingIt(t *testing.T) {
|
||||
require.Equal(t, "new-hash", wf.Artifacts[0].Files[0].Hash)
|
||||
|
||||
// The selected source's git config must be left untouched.
|
||||
selected, err := store.Source().Read(newSourceID)
|
||||
selected, err := store.Source().Read(adminUserContext, newSourceID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "https://github.com/example/repo", selected.Git.URL)
|
||||
require.Equal(t, "selected-user", selected.Git.Authentication.Username)
|
||||
@@ -876,7 +805,7 @@ func TestUpdateArtifactFileForEdgeStack_MultipleArtifactsOnlyMatchingUpdated(t *
|
||||
var srcID portainer.SourceID
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{Type: portainer.SourceTypeGit, Git: &gittypes.RepoConfig{URL: "https://example.com"}}
|
||||
err := tx.Source().Create(src)
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
srcID = src.ID
|
||||
|
||||
@@ -919,7 +848,7 @@ func TestSaveWorkflowArtifact_SameSourceUpdatesArtifactOnly(t *testing.T) {
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/repo"},
|
||||
}
|
||||
err := tx.Source().Create(src)
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
sourceID = src.ID
|
||||
|
||||
@@ -971,7 +900,7 @@ func TestGitSourceAndArtifactForStack_MultipleArtifactsReturnsCorrectOne(t *test
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/shared-repo"},
|
||||
}
|
||||
err := tx.Source().Create(gitSrc)
|
||||
err := tx.Source().Create(adminUserContext, gitSrc)
|
||||
require.NoError(t, err)
|
||||
|
||||
wf := &portainer.Workflow{
|
||||
@@ -992,7 +921,7 @@ func TestGitSourceAndArtifactForStack_MultipleArtifactsReturnsCorrectOne(t *test
|
||||
var file *portainer.ArtifactFile
|
||||
err = store.ViewTx(func(tx dataservices.DataStoreTx) error {
|
||||
var txErr error
|
||||
src, file, txErr = GitSourceAndArtifactForStack(tx, workflowID, 20)
|
||||
src, file, txErr = GitSourceAndArtifactForStack(tx, adminUserContext, workflowID, 20)
|
||||
return txErr
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -1012,7 +941,7 @@ func TestGitSourceAndArtifactForEdgeStack_MultipleArtifactsReturnsCorrectOne(t *
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/shared-edge-repo"},
|
||||
}
|
||||
err := tx.Source().Create(gitSrc)
|
||||
err := tx.Source().Create(adminUserContext, gitSrc)
|
||||
require.NoError(t, err)
|
||||
|
||||
wf := &portainer.Workflow{
|
||||
@@ -1033,7 +962,7 @@ func TestGitSourceAndArtifactForEdgeStack_MultipleArtifactsReturnsCorrectOne(t *
|
||||
var file *portainer.ArtifactFile
|
||||
err = store.ViewTx(func(tx dataservices.DataStoreTx) error {
|
||||
var txErr error
|
||||
src, file, txErr = GitSourceAndArtifactForEdgeStack(tx, workflowID, 20)
|
||||
src, file, txErr = GitSourceAndArtifactForEdgeStack(tx, adminUserContext, workflowID, 20)
|
||||
return txErr
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -1070,7 +999,7 @@ func TestFindOrCreateGitSource_StripsEmbeddedCredentialsFromURL(t *testing.T) {
|
||||
var src *portainer.Source
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
var txErr error
|
||||
src, txErr = FindOrCreateGitSource(tx, &portainer.Source{
|
||||
src, txErr = FindOrCreateGitSource(tx, adminUserContext, &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
URL: "https://user:secret@github.com/example/repo",
|
||||
@@ -1081,97 +1010,3 @@ func TestFindOrCreateGitSource_StripsEmbeddedCredentialsFromURL(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "https://github.com/example/repo", src.Git.URL)
|
||||
}
|
||||
|
||||
func newSourceWithAuth(url, username, password string) *portainer.Source {
|
||||
return &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
URL: url,
|
||||
Authentication: &gittypes.GitAuthentication{
|
||||
Username: username,
|
||||
Password: password,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newAuthlessSource(url string) *portainer.Source {
|
||||
return &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: url},
|
||||
}
|
||||
}
|
||||
|
||||
func validateUniqueSourceInStore(t *testing.T, store *datastore.Store, url, username, password string, sourceID portainer.SourceID) bool {
|
||||
t.Helper()
|
||||
|
||||
var isUnique bool
|
||||
require.NoError(t, store.ViewTx(func(tx dataservices.DataStoreTx) error {
|
||||
var err error
|
||||
isUnique, err = ValidateUniqueSource(tx, url, username, password, sourceID)
|
||||
return err
|
||||
}))
|
||||
|
||||
return isUnique
|
||||
}
|
||||
|
||||
func TestValidateUniqueSource_SameURLAndCreds_IsDuplicate(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
return tx.Source().Create(newSourceWithAuth("https://github.com/org/repo.git", "alice", "secret"))
|
||||
}))
|
||||
|
||||
require.False(t, validateUniqueSourceInStore(t, store, "https://github.com/org/repo.git", "alice", "secret", 0))
|
||||
}
|
||||
|
||||
func TestValidateUniqueSource_SameURLDifferentCreds_IsUnique(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
return tx.Source().Create(newSourceWithAuth("https://github.com/org/repo.git", "alice", "secret"))
|
||||
}))
|
||||
|
||||
require.True(t, validateUniqueSourceInStore(t, store, "https://github.com/org/repo.git", "bob", "other", 0))
|
||||
}
|
||||
|
||||
func TestValidateUniqueSource_TwoAuthlessSameURL_IsDuplicate(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
return tx.Source().Create(newAuthlessSource("https://github.com/org/repo.git"))
|
||||
}))
|
||||
|
||||
require.False(t, validateUniqueSourceInStore(t, store, "https://github.com/org/repo.git", "", "", 0))
|
||||
}
|
||||
|
||||
func TestValidateUniqueSource_AuthlessVsAuthenticated_IsUnique(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
return tx.Source().Create(newAuthlessSource("https://github.com/org/repo.git"))
|
||||
}))
|
||||
|
||||
require.True(t, validateUniqueSourceInStore(t, store, "https://github.com/org/repo.git", "alice", "secret", 0))
|
||||
}
|
||||
|
||||
func TestValidateUniqueSource_ExcludesSelf(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
var srcID portainer.SourceID
|
||||
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := newSourceWithAuth("https://github.com/org/repo.git", "alice", "secret")
|
||||
if err := tx.Source().Create(src); err != nil {
|
||||
return err
|
||||
}
|
||||
srcID = src.ID
|
||||
return nil
|
||||
}))
|
||||
|
||||
require.True(t, validateUniqueSourceInStore(t, store, "https://github.com/org/repo.git", "alice", "secret", srcID))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user