mirror of
https://github.com/portainer/portainer.git
synced 2026-08-07 09:54:49 +00:00
feat(gitops): simplify the struct for Git sources BE-12919 (#2930)
This commit is contained in:
@@ -50,19 +50,19 @@ func Test_SanitizeAccesses_Admin(t *testing.T) {
|
||||
|
||||
test(nil, nil, errInvalidSource)
|
||||
test(&portainer.Source{}, nil, noError)
|
||||
test(&portainer.Source{Git: &gittypes.RepoConfig{}}, nil,
|
||||
test(&portainer.Source{Git: &gittypes.GitSource{}}, nil,
|
||||
noError, emptyUsers, emptyTeams, adminOnly(true), noOwner, public(false),
|
||||
)
|
||||
test(&portainer.Source{Git: &gittypes.RepoConfig{}, Public: true}, nil,
|
||||
test(&portainer.Source{Git: &gittypes.GitSource{}, Public: true}, nil,
|
||||
noError, emptyUsers, emptyTeams, adminOnly(false), noOwner, public(true),
|
||||
)
|
||||
test(&portainer.Source{Git: &gittypes.RepoConfig{}, AdministratorsOnly: true}, nil,
|
||||
test(&portainer.Source{Git: &gittypes.GitSource{}, AdministratorsOnly: true}, nil,
|
||||
noError, emptyUsers, emptyTeams, adminOnly(true), noOwner, public(false),
|
||||
)
|
||||
test(&portainer.Source{Git: &gittypes.RepoConfig{}, AdministratorsOnly: true, Public: true}, nil,
|
||||
test(&portainer.Source{Git: &gittypes.GitSource{}, AdministratorsOnly: true, Public: true}, nil,
|
||||
noError, emptyUsers, emptyTeams, adminOnly(true), noOwner, public(false),
|
||||
)
|
||||
test(&portainer.Source{Git: &gittypes.RepoConfig{}, AdministratorsOnly: true, Public: true, UserAccesses: []portainer.UserID{1, 2}}, nil,
|
||||
test(&portainer.Source{Git: &gittypes.GitSource{}, AdministratorsOnly: true, Public: true, UserAccesses: []portainer.UserID{1, 2}}, nil,
|
||||
noError, emptyUsers, emptyTeams, adminOnly(true), noOwner, public(false),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ func (service ServiceTx) FindOrCreateGitSource(context UserContext, src *portain
|
||||
toCreate := &portainer.Source{
|
||||
Name: src.Name,
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: src.Git.URL,
|
||||
Authentication: src.Git.Authentication,
|
||||
TLSSkipVerify: src.Git.TLSSkipVerify,
|
||||
|
||||
@@ -116,7 +116,7 @@ func (m *Migrator) migrateGitConfigToSources_2_43_0() error {
|
||||
sourcesByKey := make(map[sourceDedupeKey]portainer.SourceID, len(existingSources))
|
||||
for _, src := range existingSources {
|
||||
if src.Git != nil {
|
||||
sourcesByKey[gitSourceKey(src.Git)] = src.ID
|
||||
sourcesByKey[gitSourceKey(&gittypes.RepoConfig{URL: src.Git.URL, Authentication: src.Git.Authentication})] = src.ID
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,9 +163,13 @@ func (m *Migrator) migrateGitConfigToSources_2_43_0() error {
|
||||
|
||||
if !exists {
|
||||
src := &portainer.Source{
|
||||
Name: gittypes.RepoName(cfg.URL),
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: cfg,
|
||||
Name: gittypes.RepoName(cfg.URL),
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.GitSource{
|
||||
URL: cfg.URL,
|
||||
Authentication: cfg.Authentication,
|
||||
TLSSkipVerify: cfg.TLSSkipVerify,
|
||||
},
|
||||
OwnerID: ownerId,
|
||||
Public: public,
|
||||
AdministratorsOnly: adminOnly,
|
||||
@@ -240,7 +244,7 @@ func (m *Migrator) migrateCustomTemplateGitConfigToSources_2_43_0() error {
|
||||
sourcesByKey := make(map[sourceDedupeKey]portainer.SourceID, len(existingSources))
|
||||
for _, src := range existingSources {
|
||||
if src.Git != nil {
|
||||
sourcesByKey[gitSourceKey(src.Git)] = src.ID
|
||||
sourcesByKey[gitSourceKey(&gittypes.RepoConfig{URL: src.Git.URL, Authentication: src.Git.Authentication})] = src.ID
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,13 +254,13 @@ func (m *Migrator) migrateCustomTemplateGitConfigToSources_2_43_0() error {
|
||||
continue
|
||||
}
|
||||
|
||||
cfg := &gittypes.RepoConfig{
|
||||
cfg := &gittypes.GitSource{
|
||||
URL: gittypes.SanitizeURL(t.GitConfig.URL),
|
||||
Authentication: t.GitConfig.Authentication,
|
||||
TLSSkipVerify: t.GitConfig.TLSSkipVerify,
|
||||
}
|
||||
|
||||
key := gitSourceKey(cfg)
|
||||
key := gitSourceKey(&gittypes.RepoConfig{URL: cfg.URL, Authentication: cfg.Authentication})
|
||||
|
||||
var newSrcID portainer.SourceID
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ func TestMigrateGitConfigToSources_2_43_0_GitStackMigrated(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, portainer.SourceTypeGit, src.Type)
|
||||
require.Equal(t, gitStack.GitConfig.URL, src.Git.URL)
|
||||
require.Equal(t, gitStack.GitConfig.ReferenceName, src.Git.ReferenceName)
|
||||
require.Equal(t, gitStack.GitConfig.ReferenceName, wf.Artifacts[0].Files[0].Ref)
|
||||
}
|
||||
|
||||
func TestMigrateGitConfigToSources_2_43_0_NonGitStackUntouched(t *testing.T) {
|
||||
|
||||
@@ -35,6 +35,42 @@ type RepoConfig struct {
|
||||
TLSSkipVerify bool `example:"false"`
|
||||
}
|
||||
|
||||
// GitSource holds the shared connection fields stored on a Source.
|
||||
// Per-file fields (ref, path, hash) are stored on ArtifactFile instead.
|
||||
type GitSource struct {
|
||||
URL string `example:"https://github.com/portainer/portainer.git"`
|
||||
Authentication *GitAuthentication `json:",omitempty"`
|
||||
TLSSkipVerify bool `example:"false"`
|
||||
}
|
||||
|
||||
// ToRepoConfig returns a RepoConfig populated with the connection fields from gc
|
||||
func (gc *GitSource) ToRepoConfig() *RepoConfig {
|
||||
return &RepoConfig{
|
||||
URL: gc.URL,
|
||||
Authentication: gc.Authentication,
|
||||
TLSSkipVerify: gc.TLSSkipVerify,
|
||||
}
|
||||
}
|
||||
|
||||
// SanitizeGitSource returns a copy of gc with the URL sanitized and password cleared,
|
||||
// safe to return to clients
|
||||
func SanitizeGitSource(gc *GitSource) *GitSource {
|
||||
if gc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := *gc
|
||||
result.URL = SanitizeURL(result.URL)
|
||||
|
||||
if result.Authentication != nil && result.Authentication.Password != "" {
|
||||
auth := *result.Authentication
|
||||
auth.Password = ""
|
||||
result.Authentication = &auth
|
||||
}
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
// RepoName extracts the repository name from a git URL for use as a display name.
|
||||
// e.g. "https://github.com/org/app-config.git" results in "app-config"
|
||||
func RepoName(rawURL string) string {
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestResolveRepoConfig_WithSourceID_ReturnsSourceConfig(t *testing.T) {
|
||||
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/org/repo",
|
||||
TLSSkipVerify: true,
|
||||
Authentication: &gittypes.GitAuthentication{
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestValidateSourceForStack_ValidGitSource_ReturnsNil(t *testing.T) {
|
||||
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/org/repo"},
|
||||
Git: &gittypes.GitSource{URL: "https://github.com/org/repo"},
|
||||
}
|
||||
require.NoError(t, store.Source().Create(adminUserContext, src))
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ func mustCreateGitWorkflow(t *testing.T, tx dataservices.DataStoreTx, stack *por
|
||||
|
||||
cfg := stack.GitConfig
|
||||
|
||||
src := &portainer.Source{Type: portainer.SourceTypeGit, Git: cfg}
|
||||
src := &portainer.Source{Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: cfg.URL, Authentication: cfg.Authentication, TLSSkipVerify: cfg.TLSSkipVerify}}
|
||||
require.NoError(t, tx.Source().Create(adminUserContext, src))
|
||||
|
||||
wf := &portainer.Workflow{Artifacts: []portainer.Artifact{{
|
||||
@@ -203,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(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"}}))
|
||||
require.NoError(t, tx.Source().Create(adminUserContext, &portainer.Source{Name: "source-1", Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: "http://github.com/org/repo1"}}))
|
||||
require.NoError(t, tx.Source().Create(adminUserContext, &portainer.Source{Name: "source-2", Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: "http://github.com/org/repo2"}}))
|
||||
|
||||
return tx.User().Create(&portainer.User{ID: 1, Role: portainer.AdministratorRole})
|
||||
}))
|
||||
@@ -227,7 +227,7 @@ 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, Git: &gittypes.RepoConfig{URL: "http://github.com/org/repo"}}
|
||||
src := &portainer.Source{Name: "shared", Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: "http://github.com/org/repo"}}
|
||||
require.NoError(t, tx.Source().Create(adminUserContext, src))
|
||||
srcID = src.ID
|
||||
|
||||
@@ -265,7 +265,7 @@ 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, Git: &gittypes.RepoConfig{URL: "http://github.com/org/repo"}}
|
||||
src := &portainer.Source{Name: "unused", Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: "http://github.com/org/repo"}}
|
||||
require.NoError(t, tx.Source().Create(adminUserContext, src))
|
||||
unusedID = src.ID
|
||||
|
||||
|
||||
@@ -193,7 +193,11 @@ func SaveWorkflowGitConfig(tx gitSourceStore, userContext source.UserContext, wo
|
||||
newSrc, err := FindOrCreateGitSource(tx, userContext, &portainer.Source{
|
||||
Name: gittypes.RepoName(cfg.URL),
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: cfg,
|
||||
Git: &gittypes.GitSource{
|
||||
URL: cfg.URL,
|
||||
Authentication: cfg.Authentication,
|
||||
TLSSkipVerify: cfg.TLSSkipVerify,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find or create source: %w", err)
|
||||
|
||||
@@ -28,7 +28,7 @@ func TestMergeSourceAndFile_NilFileLeaveFileFieldsEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
src := &portainer.Source{
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/example/repo",
|
||||
TLSSkipVerify: true,
|
||||
Authentication: &gittypes.GitAuthentication{
|
||||
@@ -52,7 +52,7 @@ func TestMergeSourceAndFile_MergesAllFieldsFromFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
src := &portainer.Source{
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/example/repo",
|
||||
TLSSkipVerify: true,
|
||||
},
|
||||
@@ -96,7 +96,7 @@ func TestGitSourceAndArtifactForStack_ReturnsMatchingSourceAndFile(t *testing.T)
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
gitSrc := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/repo"},
|
||||
Git: &gittypes.GitSource{URL: "https://github.com/example/repo"},
|
||||
}
|
||||
err := tx.Source().Create(adminUserContext, gitSrc)
|
||||
require.NoError(t, err)
|
||||
@@ -144,7 +144,7 @@ func TestGitSourceAndArtifactForStack_NoMatchingArtifactReturnsNil(t *testing.T)
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/repo"},
|
||||
Git: &gittypes.GitSource{URL: "https://github.com/example/repo"},
|
||||
}
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
@@ -199,7 +199,7 @@ func TestGitSourceAndArtifactForEdgeStack_ReturnsMatchingSourceAndFile(t *testin
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
gitSrc := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/edge-repo"},
|
||||
Git: &gittypes.GitSource{URL: "https://github.com/example/edge-repo"},
|
||||
}
|
||||
err := tx.Source().Create(adminUserContext, gitSrc)
|
||||
require.NoError(t, err)
|
||||
@@ -243,7 +243,7 @@ func TestUpdateArtifactFileForStack_NoMatchingArtifactIsNoOp(t *testing.T) {
|
||||
var workflowID portainer.WorkflowID
|
||||
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"}}
|
||||
src := &portainer.Source{Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: "https://example.com"}}
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
sourceID = src.ID
|
||||
@@ -281,7 +281,7 @@ func TestUpdateArtifactFileForStack_AppliesFnAndPersists(t *testing.T) {
|
||||
var workflowID portainer.WorkflowID
|
||||
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"}}
|
||||
src := &portainer.Source{Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: "https://example.com"}}
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
sourceID = src.ID
|
||||
@@ -319,7 +319,7 @@ func TestUpdateArtifactFileForEdgeStack_AppliesFnAndPersists(t *testing.T) {
|
||||
var workflowID portainer.WorkflowID
|
||||
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"}}
|
||||
src := &portainer.Source{Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: "https://example.com"}}
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
sourceID = src.ID
|
||||
@@ -360,7 +360,7 @@ func TestFindOrCreateGitSource_CreatesNewSource(t *testing.T) {
|
||||
src, txErr = FindOrCreateGitSource(tx, adminUserContext, &portainer.Source{
|
||||
Name: "my-repo",
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/example/repo",
|
||||
},
|
||||
})
|
||||
@@ -379,7 +379,7 @@ func TestFindOrCreateGitSource_ReusesExistingSourceForSameURLAndAuth(t *testing.
|
||||
makeSource := func(tx dataservices.DataStoreTx) (*portainer.Source, error) {
|
||||
return FindOrCreateGitSource(tx, adminUserContext, &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/example/repo",
|
||||
},
|
||||
})
|
||||
@@ -422,7 +422,7 @@ func TestFindOrCreateGitSource_DifferentAuthCreatesNewSource(t *testing.T) {
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
_, txErr := FindOrCreateGitSource(tx, adminUserContext, &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/example/repo",
|
||||
Authentication: &gittypes.GitAuthentication{Username: "alice", Password: "pass1"},
|
||||
},
|
||||
@@ -434,7 +434,7 @@ func TestFindOrCreateGitSource_DifferentAuthCreatesNewSource(t *testing.T) {
|
||||
err = store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
_, txErr := FindOrCreateGitSource(tx, adminUserContext, &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/example/repo",
|
||||
Authentication: &gittypes.GitAuthentication{Username: "bob", Password: "pass2"},
|
||||
},
|
||||
@@ -458,7 +458,7 @@ func TestSaveWorkflowGitConfig_UpdatesFileAndSourceWhenURLUnchanged(t *testing.T
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/example/repo",
|
||||
TLSSkipVerify: false,
|
||||
Authentication: &gittypes.GitAuthentication{
|
||||
@@ -533,7 +533,7 @@ func TestSaveWorkflowGitConfig_CreatesNewSourceOnURLChange(t *testing.T) {
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/old-repo"},
|
||||
Git: &gittypes.GitSource{URL: "https://github.com/example/old-repo"},
|
||||
}
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
@@ -582,7 +582,7 @@ func TestSaveWorkflowGitConfig_ReusesExistingSourceOnURLChange(t *testing.T) {
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
old := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/old-repo"},
|
||||
Git: &gittypes.GitSource{URL: "https://github.com/example/old-repo"},
|
||||
}
|
||||
err := tx.Source().Create(adminUserContext, old)
|
||||
require.NoError(t, err)
|
||||
@@ -590,7 +590,7 @@ func TestSaveWorkflowGitConfig_ReusesExistingSourceOnURLChange(t *testing.T) {
|
||||
|
||||
existing := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/shared-repo"},
|
||||
Git: &gittypes.GitSource{URL: "https://github.com/example/shared-repo"},
|
||||
}
|
||||
err = tx.Source().Create(adminUserContext, existing)
|
||||
require.NoError(t, err)
|
||||
@@ -638,7 +638,7 @@ func TestSaveWorkflowGitConfig_OnlyMatchingArtifactUpdated(t *testing.T) {
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/repo"},
|
||||
Git: &gittypes.GitSource{URL: "https://github.com/example/repo"},
|
||||
}
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
@@ -687,7 +687,7 @@ func TestUpdateArtifactFileForStack_MultipleArtifactsOnlyMatchingUpdated(t *test
|
||||
var workflowID portainer.WorkflowID
|
||||
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"}}
|
||||
src := &portainer.Source{Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: "https://example.com"}}
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
srcID = src.ID
|
||||
@@ -731,7 +731,7 @@ func TestSaveWorkflowArtifact_SwitchesSourceWithoutMutatingIt(t *testing.T) {
|
||||
// resolution would fail to switch.
|
||||
old := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/repo"},
|
||||
Git: &gittypes.GitSource{URL: "https://github.com/example/repo"},
|
||||
}
|
||||
err := tx.Source().Create(adminUserContext, old)
|
||||
require.NoError(t, err)
|
||||
@@ -739,7 +739,7 @@ func TestSaveWorkflowArtifact_SwitchesSourceWithoutMutatingIt(t *testing.T) {
|
||||
|
||||
selected := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/example/repo",
|
||||
Authentication: &gittypes.GitAuthentication{
|
||||
Username: "selected-user",
|
||||
@@ -804,7 +804,7 @@ func TestUpdateArtifactFileForEdgeStack_MultipleArtifactsOnlyMatchingUpdated(t *
|
||||
var workflowID portainer.WorkflowID
|
||||
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"}}
|
||||
src := &portainer.Source{Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: "https://example.com"}}
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
srcID = src.ID
|
||||
@@ -846,7 +846,7 @@ func TestSaveWorkflowArtifact_SameSourceUpdatesArtifactOnly(t *testing.T) {
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/repo"},
|
||||
Git: &gittypes.GitSource{URL: "https://github.com/example/repo"},
|
||||
}
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
@@ -898,7 +898,7 @@ func TestGitSourceAndArtifactForStack_MultipleArtifactsReturnsCorrectOne(t *test
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
gitSrc := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/shared-repo"},
|
||||
Git: &gittypes.GitSource{URL: "https://github.com/example/shared-repo"},
|
||||
}
|
||||
err := tx.Source().Create(adminUserContext, gitSrc)
|
||||
require.NoError(t, err)
|
||||
@@ -939,7 +939,7 @@ func TestGitSourceAndArtifactForEdgeStack_MultipleArtifactsReturnsCorrectOne(t *
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
gitSrc := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/shared-edge-repo"},
|
||||
Git: &gittypes.GitSource{URL: "https://github.com/example/shared-edge-repo"},
|
||||
}
|
||||
err := tx.Source().Create(adminUserContext, gitSrc)
|
||||
require.NoError(t, err)
|
||||
@@ -975,17 +975,10 @@ func TestGitSourceAndArtifactForEdgeStack_MultipleArtifactsReturnsCorrectOne(t *
|
||||
func TestMergeSourceAndFile_ConfigHashComesFromFileNotSource(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// ConfigHash must come from ArtifactFile.Hash, not src.Git.
|
||||
// A Source shared by two stacks has one Git.ConfigHash field;
|
||||
// if reads used it instead of ArtifactFile.Hash they would clobber each other.
|
||||
src := &portainer.Source{
|
||||
Git: &gittypes.RepoConfig{
|
||||
URL: "https://github.com/example/repo",
|
||||
},
|
||||
}
|
||||
file := &portainer.ArtifactFile{
|
||||
Hash: "artifact-hash",
|
||||
Git: &gittypes.GitSource{URL: "https://github.com/example/repo"},
|
||||
}
|
||||
file := &portainer.ArtifactFile{Hash: "artifact-hash"}
|
||||
|
||||
cfg := MergeSourceAndFile(src, file)
|
||||
require.NotNil(t, cfg)
|
||||
@@ -1001,7 +994,7 @@ func TestFindOrCreateGitSource_StripsEmbeddedCredentialsFromURL(t *testing.T) {
|
||||
var txErr error
|
||||
src, txErr = FindOrCreateGitSource(tx, adminUserContext, &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://user:secret@github.com/example/repo",
|
||||
},
|
||||
})
|
||||
|
||||
@@ -340,7 +340,7 @@ func (handler *Handler) createCustomTemplateFromGitRepository(r *http.Request) (
|
||||
src, err := workflows.FindOrCreateGitSource(handler.DataStore, userContext, &portainer.Source{
|
||||
Name: gittypes.RepoName(gitConfig.URL),
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: gitConfig.URL,
|
||||
Authentication: gitConfig.Authentication,
|
||||
TLSSkipVerify: gitConfig.TLSSkipVerify,
|
||||
|
||||
@@ -1115,7 +1115,7 @@ func TestCustomTemplateCreate_FromRepository_WithSourceID_Success(t *testing.T)
|
||||
src := &portainer.Source{
|
||||
Name: "example/repo",
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/example/repo",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ func TestCustomTemplateFile_GitTemplate(t *testing.T) {
|
||||
require.NoError(t, ds.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/example/repo"},
|
||||
Git: &gittypes.GitSource{URL: "https://github.com/example/repo"},
|
||||
}
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -177,7 +177,7 @@ func Test_customTemplateGitFetch(t *testing.T) {
|
||||
ID: 1,
|
||||
Type: portainer.SourceTypeGit,
|
||||
Public: true,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/example/repo",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ func TestInspectHandler_GitConfigPopulatedFromSource(t *testing.T) {
|
||||
require.NoError(t, ds.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/example/repo",
|
||||
TLSSkipVerify: true,
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@ func TestCustomTemplateList_PopulatesGitConfigFromSource(t *testing.T) {
|
||||
require.NoError(t, ds.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/example/repo",
|
||||
TLSSkipVerify: true,
|
||||
},
|
||||
@@ -89,7 +89,7 @@ func TestCustomTemplateList_StripsPasswordFromGitConfig(t *testing.T) {
|
||||
require.NoError(t, ds.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/example/repo",
|
||||
Authentication: &gittypes.GitAuthentication{
|
||||
Username: "user",
|
||||
|
||||
@@ -237,7 +237,7 @@ func (handler *Handler) customTemplateUpdate(w http.ResponseWriter, r *http.Requ
|
||||
src, err := workflows.FindOrCreateGitSource(handler.DataStore, userContext, &portainer.Source{
|
||||
Name: gittypes.RepoName(gitConfig.URL),
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: gitConfig.URL,
|
||||
Authentication: gitConfig.Authentication,
|
||||
TLSSkipVerify: gitConfig.TLSSkipVerify,
|
||||
|
||||
@@ -480,7 +480,7 @@ func TestCustomTemplateUpdate_WithSourceID_Success(t *testing.T) {
|
||||
src := &portainer.Source{
|
||||
Name: "example/repo",
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/example/repo",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -7,6 +7,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"
|
||||
)
|
||||
|
||||
func populateGitConfig(tx dataservices.DataStoreTx, userContext source.UserContext, template *portainer.CustomTemplate) {
|
||||
@@ -21,10 +22,14 @@ func populateGitConfig(tx dataservices.DataStoreTx, userContext source.UserConte
|
||||
return
|
||||
}
|
||||
|
||||
cfg := *src.Git
|
||||
cfg.ReferenceName = file.Ref
|
||||
cfg.ConfigFilePath = file.Path
|
||||
cfg.ConfigHash = file.Hash
|
||||
cfg := &gittypes.RepoConfig{
|
||||
URL: src.Git.URL,
|
||||
Authentication: src.Git.Authentication,
|
||||
TLSSkipVerify: src.Git.TLSSkipVerify,
|
||||
ReferenceName: file.Ref,
|
||||
ConfigFilePath: file.Path,
|
||||
ConfigHash: file.Hash,
|
||||
}
|
||||
|
||||
if cfg.Authentication != nil {
|
||||
sanitized := *cfg.Authentication
|
||||
@@ -32,7 +37,7 @@ func populateGitConfig(tx dataservices.DataStoreTx, userContext source.UserConte
|
||||
cfg.Authentication = &sanitized
|
||||
}
|
||||
|
||||
template.GitConfig = &cfg
|
||||
template.GitConfig = cfg
|
||||
}
|
||||
|
||||
// IsValidNote reports whether note is safe to display. Notes containing <img> tags are rejected.
|
||||
|
||||
@@ -58,7 +58,7 @@ func TestPopulateGitConfig_PopulatesFromSourceAndArtifact(t *testing.T) {
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/example/repo",
|
||||
TLSSkipVerify: true,
|
||||
},
|
||||
@@ -106,7 +106,7 @@ func TestPopulateGitConfig_StripsPassword(t *testing.T) {
|
||||
err := store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/example/repo",
|
||||
Authentication: &gittypes.GitAuthentication{
|
||||
Username: "user",
|
||||
|
||||
@@ -89,7 +89,7 @@ func (h *Handler) gitSourceCreate(w http.ResponseWriter, r *http.Request) *httpe
|
||||
return httperror.InternalServerError("Unable to create source", err)
|
||||
}
|
||||
|
||||
src.Git = gittypes.SanitizeRepoConfig(src.Git)
|
||||
src.Git = gittypes.SanitizeGitSource(src.Git)
|
||||
|
||||
return response.JSONWithStatus(w, src, http.StatusCreated)
|
||||
}
|
||||
@@ -112,7 +112,7 @@ func BuildBaseGitSource(payload GitSourceCreatePayload) *portainer.Source {
|
||||
return &portainer.Source{
|
||||
Name: name,
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: payload.URL,
|
||||
TLSSkipVerify: payload.TLSSkipVerify,
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestSourceDelete_Success(t *testing.T) {
|
||||
|
||||
var srcID portainer.SourceID
|
||||
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{Name: "to-delete", Type: portainer.SourceTypeGit, Git: &gittypes.RepoConfig{URL: "http://github.com/org/repo"}}
|
||||
src := &portainer.Source{Name: "to-delete", Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: "http://github.com/org/repo"}}
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
srcID = src.ID
|
||||
@@ -58,7 +58,7 @@ func TestSourceDelete_InUse(t *testing.T) {
|
||||
|
||||
var srcID portainer.SourceID
|
||||
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{Name: "in-use", Type: portainer.SourceTypeGit, Git: &gittypes.RepoConfig{URL: "http://github.com/org/repo"}}
|
||||
src := &portainer.Source{Name: "in-use", Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: "http://github.com/org/repo"}}
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
srcID = src.ID
|
||||
@@ -100,7 +100,7 @@ func TestSourceDelete_InUseByCustomTemplate(t *testing.T) {
|
||||
|
||||
var srcID portainer.SourceID
|
||||
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{Name: "in-use-by-template", Type: portainer.SourceTypeGit, Git: &gittypes.RepoConfig{URL: "http://github.com/org/repo"}}
|
||||
src := &portainer.Source{Name: "in-use-by-template", Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: "http://github.com/org/repo"}}
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
srcID = src.ID
|
||||
|
||||
@@ -37,12 +37,36 @@ func FetchSourceWorkflows(tx dataservices.DataStoreTx, src *portainer.Source) ([
|
||||
return nil, ce.SourceStats{}, err
|
||||
}
|
||||
|
||||
artifactByStack := make(map[portainer.StackID]portainer.ArtifactFile)
|
||||
for _, wf := range wfs {
|
||||
for _, artifact := range wf.Artifacts {
|
||||
if artifact.StackID == 0 {
|
||||
continue
|
||||
}
|
||||
if _, exists := artifactByStack[artifact.StackID]; exists {
|
||||
continue
|
||||
}
|
||||
for _, file := range artifact.Files {
|
||||
if file.SourceID == src.ID {
|
||||
artifactByStack[artifact.StackID] = file
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unknown := ce.WorkflowPhaseStatus{Status: ce.StatusUnknown}
|
||||
items := make([]ce.Workflow, 0, len(stacks))
|
||||
stats := ce.SourceStats{EndpointIDs: set.Set[portainer.EndpointID]{}}
|
||||
|
||||
for _, stacks := range stacks {
|
||||
items = append(items, ce.MapStackToWorkflow(stacks, src.Git, unknown, unknown))
|
||||
cfg := src.Git.ToRepoConfig()
|
||||
if file, ok := artifactByStack[stacks.ID]; ok {
|
||||
cfg.ReferenceName = file.Ref
|
||||
cfg.ConfigFilePath = file.Path
|
||||
cfg.ConfigHash = file.Hash
|
||||
}
|
||||
items = append(items, ce.MapStackToWorkflow(stacks, cfg, unknown, unknown))
|
||||
stats.WorkflowCount++
|
||||
if stacks.EndpointID != 0 {
|
||||
stats.EndpointIDs.Add(stacks.EndpointID)
|
||||
|
||||
@@ -20,7 +20,6 @@ type gitAuthInfo struct {
|
||||
}
|
||||
|
||||
type connectionInfo struct {
|
||||
ConfigFilePath string `json:"configFilePath"`
|
||||
TLSSkipVerify bool `json:"tlsSkipVerify"`
|
||||
Authentication *gitAuthInfo `json:"authentication,omitempty"`
|
||||
}
|
||||
@@ -103,7 +102,7 @@ func (h *Handler) getSource(w http.ResponseWriter, r *http.Request) *httperror.H
|
||||
return response.JSON(w, detail)
|
||||
}
|
||||
|
||||
func BuildSourceDetail(baseSource Source, cfg *gittypes.RepoConfig, sourceWfs []workflows.Workflow, access SourceAccess) SourceDetail {
|
||||
func BuildSourceDetail(baseSource Source, cfg *gittypes.GitSource, sourceWfs []workflows.Workflow, access SourceAccess) SourceDetail {
|
||||
var autoUpdate *AutoUpdateInfo
|
||||
if len(sourceWfs) > 0 {
|
||||
autoUpdate = BuildAutoUpdateInfo(sourceWfs[0].AutoUpdate)
|
||||
@@ -140,12 +139,11 @@ func BuildSourceAccess(source *portainer.Source) SourceAccess {
|
||||
}
|
||||
}
|
||||
|
||||
func buildConnectionInfo(cfg *gittypes.RepoConfig) connectionInfo {
|
||||
func buildConnectionInfo(cfg *gittypes.GitSource) connectionInfo {
|
||||
if cfg == nil {
|
||||
return connectionInfo{}
|
||||
}
|
||||
return connectionInfo{
|
||||
ConfigFilePath: cfg.ConfigFilePath,
|
||||
TLSSkipVerify: cfg.TLSSkipVerify,
|
||||
Authentication: buildGitAuthInfo(cfg.Authentication),
|
||||
}
|
||||
|
||||
@@ -33,11 +33,9 @@ func TestGetSource_ReturnsDetail(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
cfg := &gittypes.RepoConfig{
|
||||
URL: "https://github.com/org/repo",
|
||||
ReferenceName: "refs/heads/main",
|
||||
ConfigFilePath: "docker-compose.yml",
|
||||
TLSSkipVerify: true,
|
||||
cfg := &gittypes.GitSource{
|
||||
URL: "https://github.com/org/repo",
|
||||
TLSSkipVerify: true,
|
||||
}
|
||||
|
||||
var srcID portainer.SourceID
|
||||
@@ -56,7 +54,6 @@ func TestGetSource_ReturnsDetail(t *testing.T) {
|
||||
assert.Equal(t, srcID, detail.ID)
|
||||
assert.Equal(t, "repo", detail.Name)
|
||||
assert.Equal(t, 1, detail.UsedBy)
|
||||
assert.Equal(t, "docker-compose.yml", detail.Connection.ConfigFilePath)
|
||||
assert.True(t, detail.Connection.TLSSkipVerify)
|
||||
require.Len(t, detail.Workflows, 1)
|
||||
assert.Equal(t, "my-stack", detail.Workflows[0].Name)
|
||||
@@ -66,7 +63,7 @@ func TestGetSource_RedactsCredentials(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, store := datastore.MustNewTestStore(t, false, true)
|
||||
|
||||
cfg := &gittypes.RepoConfig{
|
||||
cfg := &gittypes.GitSource{
|
||||
URL: "https://github.com/org/secure",
|
||||
Authentication: &gittypes.GitAuthentication{Username: "user", Password: "s3cr3t"},
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ var adminUserContext = source.InsecureNewAdminContext()
|
||||
|
||||
// createGitWorkflow creates a Source and Workflow for the given config and
|
||||
// wires them up by setting stack.WorkflowID before creating the stack.
|
||||
func createGitWorkflow(t *testing.T, tx dataservices.DataStoreTx, stack *portainer.Stack, cfg *gittypes.RepoConfig) portainer.SourceID {
|
||||
func createGitWorkflow(t *testing.T, tx dataservices.DataStoreTx, stack *portainer.Stack, cfg *gittypes.GitSource) portainer.SourceID {
|
||||
t.Helper()
|
||||
|
||||
src := &portainer.Source{
|
||||
@@ -35,11 +35,7 @@ func createGitWorkflow(t *testing.T, tx dataservices.DataStoreTx, stack *portain
|
||||
wf := &portainer.Workflow{
|
||||
Artifacts: []portainer.Artifact{{
|
||||
StackID: stack.ID,
|
||||
Files: []portainer.ArtifactFile{{
|
||||
SourceID: src.ID,
|
||||
Path: cfg.ConfigFilePath,
|
||||
Ref: cfg.ReferenceName,
|
||||
}},
|
||||
Files: []portainer.ArtifactFile{{SourceID: src.ID}},
|
||||
}},
|
||||
}
|
||||
require.NoError(t, tx.Workflow().Create(wf))
|
||||
@@ -94,12 +90,8 @@ func decodeSourceDetail(t *testing.T, rr *httptest.ResponseRecorder) SourceDetai
|
||||
return item
|
||||
}
|
||||
|
||||
func gitCfg(url string) *gittypes.RepoConfig {
|
||||
return &gittypes.RepoConfig{
|
||||
URL: url,
|
||||
ConfigFilePath: "docker-compose.yml",
|
||||
ReferenceName: "refs/heads/main",
|
||||
}
|
||||
func gitCfg(url string) *gittypes.GitSource {
|
||||
return &gittypes.GitSource{URL: url}
|
||||
}
|
||||
|
||||
func buildCreateReq(t *testing.T, userID portainer.UserID, body []byte) *http.Request {
|
||||
|
||||
@@ -117,7 +117,7 @@ func (h *Handler) gitSourceTest(w http.ResponseWriter, r *http.Request) *httperr
|
||||
}
|
||||
|
||||
// testSourceConnection verifies that a git repository is reachable with the given config.
|
||||
func testSourceConnection(ctx context.Context, gitService portainer.GitService, config *gittypes.RepoConfig) ConnectionTestResult {
|
||||
func testSourceConnection(ctx context.Context, gitService portainer.GitService, config *gittypes.GitSource) ConnectionTestResult {
|
||||
var username, password string
|
||||
if config.Authentication != nil {
|
||||
username = config.Authentication.Username
|
||||
|
||||
@@ -42,7 +42,7 @@ func TestSourcesSummary_CountsByStatus(t *testing.T) {
|
||||
|
||||
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
for idx, name := range []string{"source-a", "source-b", "source-c"} {
|
||||
err := tx.Source().Create(adminUserContext, &portainer.Source{Name: name, Type: portainer.SourceTypeGit, Git: &gittypes.RepoConfig{URL: fmt.Sprintf("http://github.com/org/repo%d", idx)}})
|
||||
err := tx.Source().Create(adminUserContext, &portainer.Source{Name: name, Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: fmt.Sprintf("http://github.com/org/repo%d", idx)}})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,10 @@ var (
|
||||
ErrNotGitSource = errors.New("source is not a Git source")
|
||||
)
|
||||
|
||||
// GitSourceUpdatePayload holds the parameters for creating a git-backed source
|
||||
// GitSourceUpdatePayload holds the parameters for updating a git-backed source
|
||||
type GitSourceUpdatePayload struct {
|
||||
Name *string `json:"name"`
|
||||
URL *string `json:"url"`
|
||||
ReferenceName *string `json:"referenceName"`
|
||||
TLSSkipVerify *bool `json:"tlsSkipVerify"`
|
||||
Authentication *GitAuthenticationUpdatePayload `json:"authentication"`
|
||||
}
|
||||
@@ -107,7 +106,7 @@ func (h *Handler) gitSourceUpdate(w http.ResponseWriter, r *http.Request) *httpe
|
||||
return httperror.InternalServerError("Unable to update source", err)
|
||||
}
|
||||
|
||||
src.Git = gittypes.SanitizeRepoConfig(src.Git)
|
||||
src.Git = gittypes.SanitizeGitSource(src.Git)
|
||||
|
||||
return response.JSON(w, src)
|
||||
}
|
||||
@@ -144,17 +143,13 @@ func ApplyBaseGitSourceChanges(src *portainer.Source, payload GitSourceUpdatePay
|
||||
}
|
||||
|
||||
if src.Git == nil {
|
||||
src.Git = &gittypes.RepoConfig{}
|
||||
src.Git = &gittypes.GitSource{}
|
||||
}
|
||||
|
||||
if payload.URL != nil {
|
||||
src.Git.URL = *payload.URL
|
||||
}
|
||||
|
||||
if payload.ReferenceName != nil {
|
||||
src.Git.ReferenceName = *payload.ReferenceName
|
||||
}
|
||||
|
||||
if payload.TLSSkipVerify != nil {
|
||||
src.Git.TLSSkipVerify = *payload.TLSSkipVerify
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestGitSourceUpdate_Success(t *testing.T) {
|
||||
|
||||
var srcID portainer.SourceID
|
||||
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{Name: "old-name", Type: portainer.SourceTypeGit, Git: &gittypes.RepoConfig{URL: "http://github.com/org/repo"}}
|
||||
src := &portainer.Source{Name: "old-name", Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: "http://github.com/org/repo"}}
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
srcID = src.ID
|
||||
@@ -58,7 +58,7 @@ func TestGitSourceUpdate_PreservesAuthWhenNotProvided(t *testing.T) {
|
||||
src := &portainer.Source{
|
||||
Name: "auth-source",
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/org/repo.git",
|
||||
Authentication: &gittypes.GitAuthentication{
|
||||
Username: "alice",
|
||||
@@ -107,7 +107,7 @@ func TestGitSourceUpdate_ClearsAuthWhenRequested(t *testing.T) {
|
||||
src := &portainer.Source{
|
||||
Name: "auth-source",
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/org/repo.git",
|
||||
Authentication: &gittypes.GitAuthentication{
|
||||
Username: "alice",
|
||||
@@ -154,7 +154,7 @@ func TestGitSourceUpdate_ReplacesAuthWhenProvided(t *testing.T) {
|
||||
src := &portainer.Source{
|
||||
Name: "auth-source",
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/org/repo.git",
|
||||
Authentication: &gittypes.GitAuthentication{
|
||||
Username: "alice",
|
||||
@@ -225,14 +225,14 @@ func TestGitSourceUpdate_ConflictOnDuplicateURL(t *testing.T) {
|
||||
existing := &portainer.Source{
|
||||
Name: "existing",
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/org/existing.git",
|
||||
},
|
||||
}
|
||||
err := tx.Source().Create(adminUserContext, existing)
|
||||
require.NoError(t, err)
|
||||
|
||||
src := &portainer.Source{Name: "other", Type: portainer.SourceTypeGit, Git: &gittypes.RepoConfig{URL: "http://github.com/org/repo"}}
|
||||
src := &portainer.Source{Name: "other", Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: "http://github.com/org/repo"}}
|
||||
err = tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
srcID = src.ID
|
||||
@@ -259,7 +259,7 @@ func TestGitSourceUpdate_MalformedJSON(t *testing.T) {
|
||||
|
||||
var srcID portainer.SourceID
|
||||
require.NoError(t, store.UpdateTx(func(tx dataservices.DataStoreTx) error {
|
||||
src := &portainer.Source{Name: "src", Type: portainer.SourceTypeGit, Git: &gittypes.RepoConfig{URL: "http://github.com/org/repo"}}
|
||||
src := &portainer.Source{Name: "src", Type: portainer.SourceTypeGit, Git: &gittypes.GitSource{URL: "http://github.com/org/repo"}}
|
||||
err := tx.Source().Create(adminUserContext, src)
|
||||
require.NoError(t, err)
|
||||
srcID = src.ID
|
||||
@@ -284,7 +284,7 @@ func TestGitSourceUpdate_ConflictWhenAuthChangesMatchAnotherSource(t *testing.T)
|
||||
existing := &portainer.Source{
|
||||
Name: "existing",
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/org/repo.git",
|
||||
Authentication: &gittypes.GitAuthentication{
|
||||
Username: "alice",
|
||||
@@ -299,7 +299,7 @@ func TestGitSourceUpdate_ConflictWhenAuthChangesMatchAnotherSource(t *testing.T)
|
||||
other := &portainer.Source{
|
||||
Name: "other",
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/org/repo.git"},
|
||||
Git: &gittypes.GitSource{URL: "https://github.com/org/repo.git"},
|
||||
}
|
||||
if err := tx.Source().Create(adminUserContext, other); err != nil {
|
||||
return err
|
||||
|
||||
@@ -12,7 +12,7 @@ func (h *Handler) buildSource(ctx context.Context, src *portainer.Source, stats
|
||||
var status ce.Status
|
||||
var sourceErr string
|
||||
if src.Git != nil {
|
||||
phase, _ := ce.ComputeGitPhasesForConfig(ctx, h.gitService, src.Git)
|
||||
phase, _ := ce.ComputeGitPhasesForConfig(ctx, h.gitService, src.Git.ToRepoConfig())
|
||||
status = phase.Status
|
||||
sourceErr = phase.Error
|
||||
} else {
|
||||
|
||||
@@ -68,17 +68,15 @@ func TestBuildConnectionInfo(t *testing.T) {
|
||||
|
||||
assert.Equal(t, connectionInfo{}, buildConnectionInfo(nil))
|
||||
|
||||
cfg := &gittypes.RepoConfig{
|
||||
ConfigFilePath: "docker-compose.yml",
|
||||
cfg := &gittypes.GitSource{
|
||||
TLSSkipVerify: true,
|
||||
Authentication: &gittypes.GitAuthentication{Username: "user"},
|
||||
}
|
||||
got := buildConnectionInfo(cfg)
|
||||
assert.Equal(t, "docker-compose.yml", got.ConfigFilePath)
|
||||
assert.True(t, got.TLSSkipVerify)
|
||||
require.NotNil(t, got.Authentication)
|
||||
assert.Equal(t, "user", got.Authentication.Username)
|
||||
|
||||
got = buildConnectionInfo(&gittypes.RepoConfig{})
|
||||
got = buildConnectionInfo(&gittypes.GitSource{})
|
||||
assert.Nil(t, got.Authentication)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func createGitStack(t *testing.T, tx dataservices.DataStoreTx, stack *portainer.
|
||||
t.Helper()
|
||||
|
||||
if stack.GitConfig != nil {
|
||||
src := &portainer.Source{Git: stack.GitConfig, Type: portainer.SourceTypeGit}
|
||||
src := &portainer.Source{Git: &gittypes.GitSource{URL: stack.GitConfig.URL, Authentication: stack.GitConfig.Authentication, TLSSkipVerify: stack.GitConfig.TLSSkipVerify}, Type: portainer.SourceTypeGit}
|
||||
require.NoError(t, tx.Source().Create(source.InsecureNewAdminContext(), src))
|
||||
|
||||
wf := &portainer.Workflow{Artifacts: []portainer.Artifact{{
|
||||
|
||||
@@ -41,9 +41,8 @@ func TestStackFile_GitPendingRedeploy_Returns409(t *testing.T) {
|
||||
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
URL: "https://github.com/portainer/portainer.git",
|
||||
ConfigFilePath: "docker-compose.yml",
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/portainer/portainer.git",
|
||||
},
|
||||
}
|
||||
require.NoError(t, store.Source().Create(source.InsecureNewAdminContext(), src))
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestStackUpdateGitWebhookUniqueness(t *testing.T) {
|
||||
|
||||
sharedSrc := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{URL: "https://github.com/portainer/portainer.git"},
|
||||
Git: &gittypes.GitSource{URL: "https://github.com/portainer/portainer.git"},
|
||||
}
|
||||
err = store.Source().Create(source.InsecureNewAdminContext(), sharedSrc)
|
||||
require.NoError(t, err)
|
||||
|
||||
+7
-7
@@ -1321,13 +1321,13 @@ type (
|
||||
|
||||
// Source represents a GitOps source that can be referenced by stacks or deployments.
|
||||
Source struct {
|
||||
ID SourceID `json:"id" example:"1"`
|
||||
Name string `json:"name" example:"my-source"`
|
||||
LastSync int64 `json:"lastSync,omitempty" example:"1587399600"`
|
||||
Type SourceType `json:"type" example:"1"`
|
||||
Git *gittypes.RepoConfig `json:"git,omitempty"`
|
||||
Registry *Registry `json:"registry,omitempty"`
|
||||
Helm *HelmConfig `json:"helm,omitempty"`
|
||||
ID SourceID `json:"id" example:"1"`
|
||||
Name string `json:"name" example:"my-source"`
|
||||
LastSync int64 `json:"lastSync,omitempty" example:"1587399600"`
|
||||
Type SourceType `json:"type" example:"1"`
|
||||
Git *gittypes.GitSource `json:"git,omitempty"`
|
||||
Registry *Registry `json:"registry,omitempty"`
|
||||
Helm *HelmConfig `json:"helm,omitempty"`
|
||||
|
||||
Public bool `json:"public"`
|
||||
AdministratorsOnly bool `json:"administratorsOnly"`
|
||||
|
||||
@@ -203,10 +203,8 @@ func Test_redeployWhenChanged_DoesNothingWhenNoGitChanges(t *testing.T) {
|
||||
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
URL: "url",
|
||||
ReferenceName: "ref",
|
||||
ConfigHash: "oldHash",
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "url",
|
||||
},
|
||||
}
|
||||
err = store.Source().Create(adminUserContext, src)
|
||||
@@ -250,10 +248,8 @@ func Test_redeployWhenChanged_FailsWhenCannotClone(t *testing.T) {
|
||||
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
URL: "url",
|
||||
ReferenceName: "ref",
|
||||
ConfigHash: "oldHash",
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "url",
|
||||
},
|
||||
}
|
||||
err = store.Source().Create(adminUserContext, src)
|
||||
@@ -293,10 +289,8 @@ func setupRedeployStore(t *testing.T, stackType portainer.StackType) (dataservic
|
||||
|
||||
src := &portainer.Source{
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
URL: "url",
|
||||
ReferenceName: "ref",
|
||||
ConfigHash: "oldHash",
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "url",
|
||||
},
|
||||
}
|
||||
err = store.Source().Create(adminUserContext, src)
|
||||
|
||||
@@ -127,7 +127,11 @@ func (b *GitMethodStackBuilder) prepare(ctx context.Context, payload *StackPaylo
|
||||
src, err := workflows.FindOrCreateGitSource(tx, userContext, &portainer.Source{
|
||||
Name: gittypes.RepoName(repoConfig.URL),
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &repoConfig,
|
||||
Git: &gittypes.GitSource{
|
||||
URL: repoConfig.URL,
|
||||
Authentication: repoConfig.Authentication,
|
||||
TLSSkipVerify: repoConfig.TLSSkipVerify,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find or create source: %w", err)
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestGitMethodStackBuilder_WithSourceID_ReferencesExistingSource(t *testing.
|
||||
src := &portainer.Source{
|
||||
Name: "my-repo",
|
||||
Type: portainer.SourceTypeGit,
|
||||
Git: &gittypes.RepoConfig{
|
||||
Git: &gittypes.GitSource{
|
||||
URL: "https://github.com/org/private-repo",
|
||||
Authentication: &gittypes.GitAuthentication{
|
||||
Username: "git-user",
|
||||
|
||||
Reference in New Issue
Block a user