diff --git a/cmd/main.go b/cmd/main.go index 7084171f..f6d79447 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -208,6 +208,7 @@ func run(cfg *Config) error { cfg.RenderMethod, cfg.ArgocdAuthToken, cfg.ArgocdConfigPath, + cfg.RepoServerAddress, ) // Ensure cleanup is performed when we exit (e.g., stopping port forwards) diff --git a/cmd/options.go b/cmd/options.go index f3bf7187..f8c54c2e 100644 --- a/cmd/options.go +++ b/cmd/options.go @@ -88,6 +88,7 @@ var ( DefaultOutputBranchManifests = false DefaultTraverseAppOfApps = false DefaultFailOnDuplicateGeneratedApplications = false + DefaultRepoServerAddress = "" ) // RawOptions holds the raw CLI/env inputs - used only for parsing @@ -140,6 +141,7 @@ type RawOptions struct { OutputBranchManifests bool `mapstructure:"output-branch-manifests"` TraverseAppOfApps bool `mapstructure:"traverse-app-of-apps"` FailOnDuplicateGeneratedApplications bool `mapstructure:"fail-on-duplicate-generated-applications"` + RepoServerAddress string `mapstructure:"repo-server-address"` } // Config is the final, validated, ready-to-use configuration @@ -186,6 +188,7 @@ type Config struct { OutputBranchManifests bool TraverseAppOfApps bool FailOnDuplicateGeneratedApplications bool + RepoServerAddress string // Parsed/processed fields - no "parsed" prefix needed FileRegex *regexp.Regexp @@ -284,6 +287,7 @@ func Parse() *Config { viper.SetDefault("output-branch-manifests", DefaultOutputBranchManifests) viper.SetDefault("traverse-app-of-apps", DefaultTraverseAppOfApps) viper.SetDefault("fail-on-duplicate-generated-applications", DefaultFailOnDuplicateGeneratedApplications) + viper.SetDefault("repo-server-address", DefaultRepoServerAddress) // Basic flags rootCmd.Flags().BoolP("debug", "d", false, "Activate debug mode") @@ -345,6 +349,7 @@ func Parse() *Config { rootCmd.Flags().Bool("output-branch-manifests", DefaultOutputBranchManifests, "Write all application manifests per branch to a single file (output/base-branch.yaml and output/target-branch.yaml)") rootCmd.Flags().Bool("traverse-app-of-apps", DefaultTraverseAppOfApps, "Recursively render child Applications discovered in rendered manifests (app-of-apps pattern). Only supported with --render-method=repo-server-api") rootCmd.Flags().Bool("fail-on-duplicate-generated-applications", DefaultFailOnDuplicateGeneratedApplications, "Fail when a single ApplicationSet generates multiple Applications with the same name") + rootCmd.Flags().String("repo-server-address", DefaultRepoServerAddress, "Address of the Argo CD repo server (host:port). Connects directly instead of port-forwarding. Requires --render-method=repo-server-api") // Check if version flag was specified directly for _, arg := range os.Args[1:] { @@ -432,6 +437,7 @@ func (o *RawOptions) ToConfig() (*Config, error) { OutputBranchManifests: o.OutputBranchManifests, TraverseAppOfApps: o.TraverseAppOfApps, FailOnDuplicateGeneratedApplications: o.FailOnDuplicateGeneratedApplications, + RepoServerAddress: o.RepoServerAddress, } var err error @@ -502,6 +508,11 @@ func (o *RawOptions) ToConfig() (*Config, error) { } } + // --repo-server-address is only supported with repo-server-api render method + if cfg.RepoServerAddress != "" && cfg.RenderMethod != RenderMethodRepoServerAPI { + return nil, fmt.Errorf("--repo-server-address requires --render-method=repo-server-api (current: %s)", cfg.RenderMethod) + } + return cfg, nil } diff --git a/docs/options.md b/docs/options.md index e9db0c66..ff5822a8 100644 --- a/docs/options.md +++ b/docs/options.md @@ -67,6 +67,7 @@ argocd-diff-preview [FLAGS] [OPTIONS] (--repo | --repo-regex ) --t | `--redirect-target-revisions ` | `REDIRECT_TARGET_REVISIONS` | - | Comma-separated source targetRevision values to redirect to the target branch. Example: main,HEAD. By default, every targetRevision in matching repositories is redirected | | `--render-method ` | `RENDER_METHOD` | `server-api` | Manifest rendering method. Options: `cli`, `server-api`, `repo-server-api` | | `--repo-regex ` | `REPO_REGEX` | - | Advanced repository matcher for templated Argo CD repoURL values. Mutually exclusive with `--repo` | +| `--repo-server-address
` | `REPO_SERVER_ADDRESS` | - | Address of the Argo CD repo server (host:port). Connects directly instead of port-forwarding. Requires --render-method=repo-server-api" | | `--secrets-folder `, `-s` | `SECRETS_FOLDER` | `./secrets` | Secrets folder where the secrets are read from | | `--selector `, `-l` | `SELECTOR` | - | Label selector to filter on (e.g., `key1=value1,key2=value2`) | | `--timeout ` | `TIMEOUT` | `180` | Set timeout in seconds | diff --git a/pkg/argocd/argocd.go b/pkg/argocd/argocd.go index 4b477b54..d1ddbf53 100644 --- a/pkg/argocd/argocd.go +++ b/pkg/argocd/argocd.go @@ -58,9 +58,10 @@ type ArgoCDInstallation struct { LoginOptions string renderMode vars.RenderMethod operations Operations // CLI or API implementation + RepoServerAddress string } -func New(client *k8s.Client, namespace string, version string, repoName string, repoURL string, repoUsername string, repoPassword string, loginOptions string, renderMode vars.RenderMethod, authToken string, configPath string) *ArgoCDInstallation { +func New(client *k8s.Client, namespace string, version string, repoName string, repoURL string, repoUsername string, repoPassword string, loginOptions string, renderMode vars.RenderMethod, authToken string, configPath string, repoServerAddress string) *ArgoCDInstallation { return &ArgoCDInstallation{ K8sClient: client, Namespace: namespace, @@ -73,6 +74,7 @@ func New(client *k8s.Client, namespace string, version string, repoName string, LoginOptions: loginOptions, renderMode: renderMode, operations: NewOperations(renderMode, client, namespace, loginOptions, authToken), + RepoServerAddress: repoServerAddress, } } diff --git a/pkg/reposerverextract/appofapps.go b/pkg/reposerverextract/appofapps.go index f522d4c1..f014f638 100644 --- a/pkg/reposerverextract/appofapps.go +++ b/pkg/reposerverextract/appofapps.go @@ -196,12 +196,16 @@ func RenderApplicationsFromBothBranchesWithAppOfApps( // Create a single repo server client shared across all goroutines. // EnsurePortForward is idempotent and mutex-protected inside the client. - repoClient := reposerver.NewClient(argocd.K8sClient, argocd.Namespace) - defer repoClient.Cleanup() - - if err := repoClient.EnsurePortForward(); err != nil { - return nil, nil, time.Since(startTime), fmt.Errorf("failed to set up port forward to repo server: %w", err) + var repoClient *reposerver.Client + if argocd.RepoServerAddress != "" { + repoClient = reposerver.NewClientWithAddress(argocd.RepoServerAddress, false, true) + } else { + repoClient = reposerver.NewClient(argocd.K8sClient, argocd.Namespace) + if err := repoClient.EnsurePortForward(); err != nil { + return nil, nil, time.Since(startTime), fmt.Errorf("failed to set up port forward to repo server: %w", err) + } } + defer repoClient.Cleanup() log.Info().Msgf("🤖 Rendering Applications via repo server with app-of-apps traversal (timeout in %d seconds)", timeout) diff --git a/pkg/reposerverextract/extract.go b/pkg/reposerverextract/extract.go index 569a7e29..a5133e86 100644 --- a/pkg/reposerverextract/extract.go +++ b/pkg/reposerverextract/extract.go @@ -131,12 +131,16 @@ func RenderApplicationsFromBothBranches( // Create a single repo server client shared across all goroutines. // EnsurePortForward is idempotent and mutex-protected inside the client. - repoClient := reposerver.NewClient(argocd.K8sClient, argocd.Namespace) - defer repoClient.Cleanup() - - if err := repoClient.EnsurePortForward(); err != nil { - return nil, nil, time.Since(startTime), fmt.Errorf("failed to set up port forward to repo server: %w", err) + var repoClient *reposerver.Client + if argocd.RepoServerAddress != "" { + repoClient = reposerver.NewClientWithAddress(argocd.RepoServerAddress, false, true) + } else { + repoClient = reposerver.NewClient(argocd.K8sClient, argocd.Namespace) + if err := repoClient.EnsurePortForward(); err != nil { + return nil, nil, time.Since(startTime), fmt.Errorf("failed to set up port forward to repo server: %w", err) + } } + defer repoClient.Cleanup() allApps := append(baseApps, targetApps...)