diff --git a/cmd/argocd/commands/admin/app.go b/cmd/argocd/commands/admin/app.go index cd58b767dfe74..9f8d3d814907a 100644 --- a/cmd/argocd/commands/admin/app.go +++ b/cmd/argocd/commands/admin/app.go @@ -88,7 +88,7 @@ func NewGenAppSpecCommand() *cobra.Command { argocd admin app generate-spec kasane --repo https://github.com/argoproj/argocd-example-apps.git --path plugins/kasane --dest-namespace default --dest-server https://kubernetes.default.svc --config-management-plugin kasane `, Run: func(c *cobra.Command, args []string) { - apps, err := cmdutil.ConstructApps(fileURL, appName, labels, annotations, args, appOpts, c.Flags()) + apps, err := cmdutil.ConstructApps(fileURL, appName, labels, annotations, args, appOpts, c.Flags(), false) errors.CheckError(err) if len(apps) > 1 { errors.CheckError(fmt.Errorf("failed to generate spec, more than one application is not supported")) diff --git a/cmd/argocd/commands/app.go b/cmd/argocd/commands/app.go index d0da5e1781e76..78027ae60bdb1 100644 --- a/cmd/argocd/commands/app.go +++ b/cmd/argocd/commands/app.go @@ -33,6 +33,7 @@ import ( "github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless" cmdutil "github.com/argoproj/argo-cd/v2/cmd/util" + argocommon "github.com/argoproj/argo-cd/v2/common" "github.com/argoproj/argo-cd/v2/controller" argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient" "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" @@ -51,6 +52,7 @@ import ( argoio "github.com/argoproj/argo-cd/v2/util/io" "github.com/argoproj/argo-cd/v2/util/manifeststream" "github.com/argoproj/argo-cd/v2/util/text/label" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // NewApplicationCommand returns a new instance of an `argocd app` command @@ -139,7 +141,7 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra. argocdClient := headless.NewClientOrDie(clientOpts, c) - apps, err := cmdutil.ConstructApps(fileURL, appName, labels, annotations, args, appOpts, c.Flags()) + apps, err := cmdutil.ConstructApps(fileURL, appName, labels, annotations, args, appOpts, c.Flags(), false) errors.CheckError(err) for _, app := range apps { @@ -2344,10 +2346,12 @@ func printOperationResult(opState *argoappv1.OperationState) { // NewApplicationManifestsCommand returns a new instance of an `argocd app manifests` command func NewApplicationManifestsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { var ( - source string - revision string - local string - localRepoRoot string + source string + revision string + local string + localRepoRoot string + localAppManifest string + kubeVersion string ) var command = &cobra.Command{ Use: "manifests APPNAME", @@ -2360,35 +2364,56 @@ func NewApplicationManifestsCommand(clientOpts *argocdclient.ClientOptions) *cob os.Exit(1) } appName, appNs := argo.ParseFromQualifiedName(args[0], "") - clientset := headless.NewClientOrDie(clientOpts, c) - conn, appIf := clientset.NewApplicationClientOrDie() - defer argoio.Close(conn) - resources, err := appIf.ManagedResources(ctx, &application.ResourcesQuery{ - ApplicationName: &appName, - AppNamespace: &appNs, - }) - errors.CheckError(err) var unstructureds []*unstructured.Unstructured switch source { case "git": if local != "" { - app, err := appIf.Get(context.Background(), &application.ApplicationQuery{Name: &appName}) - errors.CheckError(err) + if localAppManifest != "" { + // get app from file + apps, err := cmdutil.ConstructApps(localAppManifest, "", []string{}, []string{}, args, cmdutil.AppOptions{}, c.Flags(), true) + errors.CheckError(err) + KustomizeOptions := argoappv1.KustomizeOptions{BuildOptions: "--enable-helm --load-restrictor=LoadRestrictionsNone"} + + for _, app := range apps { + projSources := []string{app.Spec.Source.RepoURL} + proj := &argoappv1.AppProject{ + Spec: argoappv1.AppProjectSpec{ + SourceRepos: projSources, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: app.Spec.Project, + }, + } - settingsConn, settingsIf := clientset.NewSettingsClientOrDie() - defer argoio.Close(settingsConn) - argoSettings, err := settingsIf.Get(context.Background(), &settings.SettingsQuery{}) - errors.CheckError(err) + unstructureds = getLocalObjects(context.Background(), app, proj, local, localRepoRoot, argocommon.LabelKeyAppInstance, kubeVersion, []string{""}, &KustomizeOptions, "") + } + } else { + clientset := headless.NewClientOrDie(clientOpts, c) + conn, appIf := clientset.NewApplicationClientOrDie() + defer argoio.Close(conn) - clusterConn, clusterIf := clientset.NewClusterClientOrDie() - defer argoio.Close(clusterConn) - cluster, err := clusterIf.Get(context.Background(), &clusterpkg.ClusterQuery{Name: app.Spec.Destination.Name, Server: app.Spec.Destination.Server}) - errors.CheckError(err) + app, err := appIf.Get(context.Background(), &application.ApplicationQuery{Name: &appName}) + errors.CheckError(err) - proj := getProject(c, clientOpts, ctx, app.Spec.Project) - unstructureds = getLocalObjects(context.Background(), app, proj.Project, local, localRepoRoot, argoSettings.AppLabelKey, cluster.ServerVersion, cluster.Info.APIVersions, argoSettings.KustomizeOptions, argoSettings.TrackingMethod) + settingsConn, settingsIf := clientset.NewSettingsClientOrDie() + defer argoio.Close(settingsConn) + argoSettings, err := settingsIf.Get(context.Background(), &settings.SettingsQuery{}) + errors.CheckError(err) + + clusterConn, clusterIf := clientset.NewClusterClientOrDie() + defer argoio.Close(clusterConn) + cluster, err := clusterIf.Get(context.Background(), &clusterpkg.ClusterQuery{Name: app.Spec.Destination.Name, Server: app.Spec.Destination.Server}) + errors.CheckError(err) + + proj := getProject(c, clientOpts, ctx, app.Spec.Project) + unstructureds = getLocalObjects(context.Background(), app, proj.Project, local, localRepoRoot, argoSettings.AppLabelKey, cluster.ServerVersion, cluster.Info.APIVersions, argoSettings.KustomizeOptions, argoSettings.TrackingMethod) + } } else if revision != "" { + clientset := headless.NewClientOrDie(clientOpts, c) + conn, appIf := clientset.NewApplicationClientOrDie() + defer argoio.Close(conn) + q := application.ApplicationManifestQuery{ Name: &appName, AppNamespace: &appNs, @@ -2403,11 +2428,29 @@ func NewApplicationManifestsCommand(clientOpts *argocdclient.ClientOptions) *cob unstructureds = append(unstructureds, obj) } } else { + clientset := headless.NewClientOrDie(clientOpts, c) + conn, appIf := clientset.NewApplicationClientOrDie() + defer argoio.Close(conn) + resources, err := appIf.ManagedResources(ctx, &application.ResourcesQuery{ + ApplicationName: &appName, + AppNamespace: &appNs, + }) + errors.CheckError(err) + targetObjs, err := targetObjects(resources.Items) errors.CheckError(err) unstructureds = targetObjs } case "live": + clientset := headless.NewClientOrDie(clientOpts, c) + conn, appIf := clientset.NewApplicationClientOrDie() + defer argoio.Close(conn) + resources, err := appIf.ManagedResources(ctx, &application.ResourcesQuery{ + ApplicationName: &appName, + AppNamespace: &appNs, + }) + errors.CheckError(err) + liveObjs, err := cmdutil.LiveObjects(resources.Items) errors.CheckError(err) unstructureds = liveObjs @@ -2427,6 +2470,8 @@ func NewApplicationManifestsCommand(clientOpts *argocdclient.ClientOptions) *cob command.Flags().StringVar(&revision, "revision", "", "Show manifests at a specific revision") command.Flags().StringVar(&local, "local", "", "If set, show locally-generated manifests. Value is the absolute path to app manifests within the manifest repo. Example: '/home/username/apps/env/app-1'.") command.Flags().StringVar(&localRepoRoot, "local-repo-root", ".", "Path to the local repository root. Used together with --local allows setting the repository root. Example: '/home/username/apps'.") + command.Flags().StringVar(&localAppManifest, "local-app-manifest", "", "Path to the local app. Used together with --local and --local-repo-root allows using the local application. Example: '/home/username/apps/app-manifest.yaml'.") + command.Flags().StringVar(&kubeVersion, "kube-version", "v1.27.0", "Target kubernetes cluster version. Used together with --local-app-manifest. Example: 'v1.26.4'.") return command } diff --git a/cmd/util/app.go b/cmd/util/app.go index d64c5ed02e6cb..6c97ba7975f0e 100644 --- a/cmd/util/app.go +++ b/cmd/util/app.go @@ -623,36 +623,62 @@ func constructAppsBaseOnName(appName string, labels, annotations, args []string, }, nil } -func constructAppsFromFileUrl(fileURL, appName string, labels, annotations, args []string, appOpts AppOptions, flags *pflag.FlagSet) ([]*argoappv1.Application, error) { +func constructAppsFromFileUrl(fileURL, appName string, labels, annotations, args []string, appOpts AppOptions, flags *pflag.FlagSet, filterByAppName bool) ([]*argoappv1.Application, error) { apps := make([]*argoappv1.Application, 0) // read uri err := readAppsFromURI(fileURL, &apps) if err != nil { return nil, err } + filteredApps := make([]*argoappv1.Application, 0) for _, app := range apps { - if len(args) == 1 && args[0] != app.Name { - return nil, fmt.Errorf("app name '%s' does not match app spec metadata.name '%s'", args[0], app.Name) - } - if appName != "" && appName != app.Name { - app.Name = appName + if filterByAppName { + if len(args) == 1 { + if appName != "" && appName != args[0] { + return nil, fmt.Errorf("--name argument '%s' does not match app name %s", appName, args[0]) + } + appName = args[0] + } + + if appName == app.Name { + SetAppSpecOptions(flags, &app.Spec, &appOpts) + SetParameterOverrides(app, appOpts.Parameters) + mergeLabels(app, labels) + setAnnotations(app, annotations) + filteredApps = append(filteredApps, app) + } + } else { + if len(args) == 1 && args[0] != app.Name { + return nil, fmt.Errorf("app name '%s' does not match app spec metadata.name '%s'", args[0], app.Name) + } + if appName != "" && appName != app.Name { + app.Name = appName + } + if app.Name == "" { + return nil, fmt.Errorf("app.Name is empty. --name argument can be used to provide app.Name") + } + SetAppSpecOptions(flags, &app.Spec, &appOpts) + SetParameterOverrides(app, appOpts.Parameters) + mergeLabels(app, labels) + setAnnotations(app, annotations) + filteredApps = append(filteredApps, app) } - if app.Name == "" { - return nil, fmt.Errorf("app.Name is empty. --name argument can be used to provide app.Name") + } + + if filterByAppName { + if len(filteredApps) == 0 { + return nil, fmt.Errorf("App name '%s' does not match any app spec in '%s'", appName, fileURL) } - SetAppSpecOptions(flags, &app.Spec, &appOpts) - SetParameterOverrides(app, appOpts.Parameters) - mergeLabels(app, labels) - setAnnotations(app, annotations) } - return apps, nil + + return filteredApps, nil } -func ConstructApps(fileURL, appName string, labels, annotations, args []string, appOpts AppOptions, flags *pflag.FlagSet) ([]*argoappv1.Application, error) { +func ConstructApps(fileURL, appName string, labels, annotations, args []string, appOpts AppOptions, flags *pflag.FlagSet, filterByAppName bool) ([]*argoappv1.Application, error) { if fileURL == "-" { return constructAppsFromStdin() } else if fileURL != "" { - return constructAppsFromFileUrl(fileURL, appName, labels, annotations, args, appOpts, flags) + return constructAppsFromFileUrl(fileURL, appName, labels, annotations, args, appOpts, flags, filterByAppName) } return constructAppsBaseOnName(appName, labels, annotations, args, appOpts, flags) } diff --git a/cmd/util/app_test.go b/cmd/util/app_test.go index 2f49a3cc4c8c4..6c92afc0821fc 100644 --- a/cmd/util/app_test.go +++ b/cmd/util/app_test.go @@ -321,7 +321,7 @@ func TestConstructAppFromStdin(t *testing.T) { os.Stdin = file - apps, err := ConstructApps("-", "test", []string{}, []string{}, []string{}, AppOptions{}, nil) + apps, err := ConstructApps("-", "test", []string{}, []string{}, []string{}, AppOptions{}, nil, false) if err := file.Close(); err != nil { log.Fatal(err) @@ -334,7 +334,7 @@ func TestConstructAppFromStdin(t *testing.T) { } func TestConstructBasedOnName(t *testing.T) { - apps, err := ConstructApps("", "test", []string{}, []string{}, []string{}, AppOptions{}, nil) + apps, err := ConstructApps("", "test", []string{}, []string{}, []string{}, AppOptions{}, nil, false) assert.NoError(t, err) assert.Equal(t, 1, len(apps)) diff --git a/docs/user-guide/commands/argocd_app_manifests.md b/docs/user-guide/commands/argocd_app_manifests.md index f73cfc5b9dd29..ac8b93dad4668 100644 --- a/docs/user-guide/commands/argocd_app_manifests.md +++ b/docs/user-guide/commands/argocd_app_manifests.md @@ -9,11 +9,13 @@ argocd app manifests APPNAME [flags] ### Options ``` - -h, --help help for manifests - --local string If set, show locally-generated manifests. Value is the absolute path to app manifests within the manifest repo. Example: '/home/username/apps/env/app-1'. - --local-repo-root string Path to the local repository root. Used together with --local allows setting the repository root. Example: '/home/username/apps'. (default ".") - --revision string Show manifests at a specific revision - --source string Source of manifests. One of: live|git (default "git") + -h, --help help for manifests + --kube-version string Target kubernetes cluster version. Used together with --local-app-manifest. Example: 'v1.26.4'. (default "v1.27.0") + --local string If set, show locally-generated manifests. Value is the absolute path to app manifests within the manifest repo. Example: '/home/username/apps/env/app-1'. + --local-app-manifest string Path to the local app. Used together with --local and --local-repo-root allows using the local application. Example: '/home/username/apps/app-manifest.yaml'. + --local-repo-root string Path to the local repository root. Used together with --local allows setting the repository root. Example: '/home/username/apps'. (default ".") + --revision string Show manifests at a specific revision + --source string Source of manifests. One of: live|git (default "git") ``` ### Options inherited from parent commands