Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions USERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ Currently, the following organizations are **officially** using Argo CD:
1. [WeMaintain](https://www.wemaintain.com/)
1. [WeMo Scooter](https://www.wemoscooter.com/)
1. [Whitehat Berlin](https://whitehat.berlin) by Guido Maria Serra +Fenaroli
1. [WhizUs](https://whizus.com)
1. [Witick](https://witick.io/)
1. [Wolffun Game](https://www.wolffungame.com/)
1. [WooliesX](https://wooliesx.com.au/)
Expand Down
26 changes: 24 additions & 2 deletions assets/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion cmd/util/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ type AppOptions struct {
drySourcePath string
syncSourceBranch string
syncSourcePath string
syncSourceRepo string
hydrateToBranch string
}

Expand All @@ -112,6 +113,7 @@ func AddAppFlags(command *cobra.Command, opts *AppOptions) {
command.Flags().StringVar(&opts.drySourcePath, "dry-source-path", "", "Path in repository to the app directory for the dry source")
command.Flags().StringVar(&opts.syncSourceBranch, "sync-source-branch", "", "The branch from which the app will sync")
command.Flags().StringVar(&opts.syncSourcePath, "sync-source-path", "", "The path in the repository from which the app will sync")
command.Flags().StringVar(&opts.syncSourceRepo, "sync-source-repo", "", "The repository URL from which the app will sync (defaults to dry-source-repo if not set)")
command.Flags().StringVar(&opts.hydrateToBranch, "hydrate-to-branch", "", "The branch to hydrate the app to")
command.Flags().IntVar(&opts.revisionHistoryLimit, "revision-history-limit", argoappv1.RevisionHistoryLimit, "How many items to keep in revision history")
command.Flags().StringVar(&opts.destServer, "dest-server", "", "K8s cluster URL (e.g. https://kubernetes.default.svc)")
Expand Down Expand Up @@ -829,12 +831,18 @@ func constructSourceHydrator(h *argoappv1.SourceHydrator, appOpts AppOptions, fl
case "sync-source-path":
ensureNotNil(appOpts.syncSourcePath != "")
h.SyncSource.Path = appOpts.syncSourcePath
case "sync-source-repo":
ensureNotNil(appOpts.syncSourceRepo != "")
h.SyncSource.RepoURL = appOpts.syncSourceRepo
case "hydrate-to-branch":
ensureNotNil(appOpts.hydrateToBranch != "")
if appOpts.hydrateToBranch == "" {
h.HydrateTo = nil
} else {
h.HydrateTo = &argoappv1.HydrateTo{TargetBranch: appOpts.hydrateToBranch}
if h.HydrateTo == nil {
h.HydrateTo = &argoappv1.HydrateTo{}
}
h.HydrateTo.TargetBranch = appOpts.hydrateToBranch
}
}
})
Expand Down
4 changes: 4 additions & 0 deletions cmd/util/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,10 @@ func Test_setAppSpecOptions(t *testing.T) {
require.NoError(t, f.SetFlag("sync-source-path", "apps"))
assert.Equal(t, "apps", f.spec.SourceHydrator.SyncSource.Path)

// Test sync-source-repo flag - this is the new field for different repository support
require.NoError(t, f.SetFlag("sync-source-repo", "https://github.com/argoproj/gitops-manifests"))
assert.Equal(t, "https://github.com/argoproj/gitops-manifests", f.spec.SourceHydrator.SyncSource.RepoURL)

require.NoError(t, f.SetFlag("hydrate-to-branch", "env/test-next"))
assert.Equal(t, "env/test-next", f.spec.SourceHydrator.HydrateTo.TargetBranch)

Expand Down
49 changes: 35 additions & 14 deletions controller/hydrator/hydrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,12 @@ func (h *Hydrator) ProcessAppHydrateQueueItem(origApp *appv1.Application) {
}

func getHydrationQueueKey(app *appv1.Application) types.HydrationQueueKey {
hydrateToSource := app.Spec.GetHydrateToSource()
key := types.HydrationQueueKey{
SourceRepoURL: git.NormalizeGitURLAllowInvalid(app.Spec.SourceHydrator.DrySource.RepoURL),
SourceTargetRevision: app.Spec.SourceHydrator.DrySource.TargetRevision,
DestinationBranch: app.Spec.GetHydrateToSource().TargetRevision,
DestinationRepoURL: git.NormalizeGitURLAllowInvalid(hydrateToSource.RepoURL),
DestinationBranch: hydrateToSource.TargetRevision,
}
return key
}
Expand All @@ -148,6 +150,7 @@ func (h *Hydrator) ProcessHydrationQueueItem(hydrationKey types.HydrationQueueKe
logCtx := log.WithFields(log.Fields{
"sourceRepoURL": hydrationKey.SourceRepoURL,
"sourceTargetRevision": hydrationKey.SourceTargetRevision,
"destinationRepoURL": hydrationKey.DestinationRepoURL,
"destinationBranch": hydrationKey.DestinationBranch,
})

Expand Down Expand Up @@ -293,11 +296,18 @@ func (h *Hydrator) getAppsForHydrationKey(hydrationKey types.HydrationQueueKey)
return relevantApps, nil
}

// hydrationDestKey is a struct key for tracking unique hydration destinations.
// Using a struct instead of string formatting better communicates intent and guarantees no collisions.
type hydrationDestKey struct {
repoURL string
path string
}

// validateApplications checks that all applications are valid for hydration.
func (h *Hydrator) validateApplications(apps []*appv1.Application) (map[string]*appv1.AppProject, map[string]error) {
projects := make(map[string]*appv1.AppProject)
errors := make(map[string]error)
uniquePaths := make(map[string]string, len(apps))
uniquePaths := make(map[hydrationDestKey]string, len(apps))

for _, app := range apps {
// Get the project for the app and validate if the app is allowed to use the source.
Expand All @@ -319,20 +329,30 @@ func (h *Hydrator) validateApplications(apps []*appv1.Application) (map[string]*
// Hydrating to root would overwrite or delete files at the top level of the repo,
// which can break other applications or shared configuration.
// Every hydrated app must write into a subdirectory instead.
destPath := app.Spec.SourceHydrator.SyncSource.Path
hydrateToSource := app.Spec.GetHydrateToSource()
destPath := hydrateToSource.Path
if IsRootPath(destPath) {
errors[app.QualifiedName()] = fmt.Errorf("app is configured to hydrate to the repository root (branch %q, path %q) which is not allowed", app.Spec.GetHydrateToSource().TargetRevision, destPath)
errors[app.QualifiedName()] = fmt.Errorf("app is configured to hydrate to the repository root (branch %q, path %q) which is not allowed", hydrateToSource.TargetRevision, destPath)
continue
}

// Validate that the destination repo is permitted in the project
destRepoPermitted := proj.IsSourcePermitted(hydrateToSource)
if !destRepoPermitted {
errors[app.QualifiedName()] = fmt.Errorf("destination repo %s is not permitted in project '%s'", hydrateToSource.RepoURL, proj.Name)
continue
}

// TODO: test the dupe detection
// TODO: normalize the path to avoid "path/.." from being treated as different from "."
if appName, ok := uniquePaths[destPath]; ok {
errors[app.QualifiedName()] = fmt.Errorf("app %s hydrator use the same destination: %v", appName, app.Spec.SourceHydrator.SyncSource.Path)
errors[appName] = fmt.Errorf("app %s hydrator use the same destination: %v", app.QualifiedName(), app.Spec.SourceHydrator.SyncSource.Path)
// Use a struct key for uniqueness since apps can hydrate to different repos
destKey := hydrationDestKey{repoURL: hydrateToSource.RepoURL, path: destPath}
if appName, ok := uniquePaths[destKey]; ok {
errors[app.QualifiedName()] = fmt.Errorf("app %s hydrator uses the same destination: repo=%s, path=%s", appName, destKey.repoURL, destKey.path)
errors[appName] = fmt.Errorf("app %s hydrator uses the same destination: repo=%s, path=%s", app.QualifiedName(), destKey.repoURL, destKey.path)
continue
}
uniquePaths[destPath] = app.QualifiedName()
uniquePaths[destKey] = app.QualifiedName()
}

// If there are any errors, return nil for projects to avoid possible partial processing.
Expand All @@ -350,14 +370,15 @@ func (h *Hydrator) hydrate(logCtx *log.Entry, apps []*appv1.Application, project
}

// These values are the same for all apps being hydrated together, so just get them from the first app.
repoURL := apps[0].Spec.GetHydrateToSource().RepoURL
destinationRepoURL := apps[0].Spec.GetHydrateToSource().RepoURL
targetBranch := apps[0].Spec.GetHydrateToSource().TargetRevision
// FIXME: As a convenience, the commit server will create the syncBranch if it does not exist. If the
// targetBranch does not exist, it will create it based on the syncBranch. On the next line, we take
// the `syncBranch` from the first app and assume that they're all configured the same. Instead, if any
// app has a different syncBranch, we should send the commit server an empty string and allow it to
// create the targetBranch as an orphan since we can't reliable determine a reasonable base.
syncBranch := apps[0].Spec.SourceHydrator.SyncSource.TargetBranch
drySourceRepoURL := apps[0].Spec.SourceHydrator.DrySource.RepoURL

// Get a static SHA revision from the first app so that all apps are hydrated from the same revision.
targetRevision, pathDetails, err := h.getManifests(context.Background(), apps[0], "", projects[apps[0].Spec.Project])
Expand Down Expand Up @@ -405,20 +426,20 @@ func (h *Hydrator) hydrate(logCtx *log.Entry, apps []*appv1.Application, project
}
}

// Get the commit metadata for the target revision.
revisionMetadata, err := h.getRevisionMetadata(context.Background(), repoURL, project, targetRevision)
// Get the commit metadata for the target revision from the dry source repo.
revisionMetadata, err := h.getRevisionMetadata(context.Background(), drySourceRepoURL, project, targetRevision)
if err != nil {
return targetRevision, "", errors, fmt.Errorf("failed to get revision metadata for %q: %w", targetRevision, err)
}

repo, err := h.dependencies.GetWriteCredentials(context.Background(), repoURL, project)
repo, err := h.dependencies.GetWriteCredentials(context.Background(), destinationRepoURL, project)
if err != nil {
return targetRevision, "", errors, fmt.Errorf("failed to get hydrator credentials: %w", err)
}
if repo == nil {
// Try without credentials.
repo = &appv1.Repository{
Repo: repoURL,
Repo: destinationRepoURL,
}
logCtx.Warn("no credentials found for repo, continuing without credentials")
}
Expand All @@ -427,7 +448,7 @@ func (h *Hydrator) hydrate(logCtx *log.Entry, apps []*appv1.Application, project
if err != nil {
return targetRevision, "", errors, fmt.Errorf("failed to get hydrated commit message template: %w", err)
}
commitMessage, errMsg := getTemplatedCommitMessage(repoURL, targetRevision, commitMessageTemplate, revisionMetadata)
commitMessage, errMsg := getTemplatedCommitMessage(drySourceRepoURL, targetRevision, commitMessageTemplate, revisionMetadata)
if errMsg != nil {
return targetRevision, "", errors, fmt.Errorf("failed to get hydrator commit templated message: %w", errMsg)
}
Expand Down
Loading
Loading