github.com/argoproj/argo-cd/v3@v3.2.1/cmd/argocd/commands/projectwindows.go (about)

     1  package commands
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strconv"
     7  	"strings"
     8  	"text/tabwriter"
     9  
    10  	"github.com/spf13/cobra"
    11  
    12  	"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/headless"
    13  	"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/utils"
    14  	argocdclient "github.com/argoproj/argo-cd/v3/pkg/apiclient"
    15  	projectpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/project"
    16  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    17  	"github.com/argoproj/argo-cd/v3/util/errors"
    18  	utilio "github.com/argoproj/argo-cd/v3/util/io"
    19  )
    20  
    21  // NewProjectWindowsCommand returns a new instance of the `argocd proj windows` command
    22  func NewProjectWindowsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
    23  	roleCommand := &cobra.Command{
    24  		Use:   "windows",
    25  		Short: "Manage a project's sync windows",
    26  		Example: `
    27  #Add a sync window to a project
    28  argocd proj windows add my-project \
    29  --schedule "0 0 * * 1-5" \
    30  --duration 3600 \
    31  --prune
    32  
    33  #Delete a sync window from a project
    34  argocd proj windows delete <project-name> <window-id>
    35  
    36  #List project sync windows
    37  argocd proj windows list <project-name>`,
    38  		Run: func(c *cobra.Command, args []string) {
    39  			c.HelpFunc()(c, args)
    40  			os.Exit(1)
    41  		},
    42  	}
    43  	roleCommand.AddCommand(NewProjectWindowsDisableManualSyncCommand(clientOpts))
    44  	roleCommand.AddCommand(NewProjectWindowsEnableManualSyncCommand(clientOpts))
    45  	roleCommand.AddCommand(NewProjectWindowsAddWindowCommand(clientOpts))
    46  	roleCommand.AddCommand(NewProjectWindowsDeleteCommand(clientOpts))
    47  	roleCommand.AddCommand(NewProjectWindowsListCommand(clientOpts))
    48  	roleCommand.AddCommand(NewProjectWindowsUpdateCommand(clientOpts))
    49  	return roleCommand
    50  }
    51  
    52  // NewProjectWindowsDisableManualSyncCommand returns a new instance of an `argocd proj windows disable-manual-sync` command
    53  func NewProjectWindowsDisableManualSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
    54  	command := &cobra.Command{
    55  		Use:   "disable-manual-sync PROJECT ID",
    56  		Short: "Disable manual sync for a sync window",
    57  		Long:  "Disable manual sync for a sync window. Requires ID which can be found by running \"argocd proj windows list PROJECT\"",
    58  		Example: `
    59  #Disable manual sync for a sync window for the Project
    60  argocd proj windows disable-manual-sync PROJECT ID
    61  
    62  #Disabling manual sync for a windows set on the default project with Id 0
    63  argocd proj windows disable-manual-sync default 0`,
    64  		Run: func(c *cobra.Command, args []string) {
    65  			ctx := c.Context()
    66  
    67  			if len(args) != 2 {
    68  				c.HelpFunc()(c, args)
    69  				os.Exit(1)
    70  			}
    71  
    72  			projName := args[0]
    73  			id, err := strconv.Atoi(args[1])
    74  			errors.CheckError(err)
    75  
    76  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
    77  			defer utilio.Close(conn)
    78  
    79  			proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
    80  			errors.CheckError(err)
    81  
    82  			for i, window := range proj.Spec.SyncWindows {
    83  				if id == i {
    84  					window.ManualSync = false
    85  				}
    86  			}
    87  
    88  			_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
    89  			errors.CheckError(err)
    90  		},
    91  	}
    92  	return command
    93  }
    94  
    95  // NewProjectWindowsEnableManualSyncCommand returns a new instance of an `argocd proj windows enable-manual-sync` command
    96  func NewProjectWindowsEnableManualSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
    97  	command := &cobra.Command{
    98  		Use:   "enable-manual-sync PROJECT ID",
    99  		Short: "Enable manual sync for a sync window",
   100  		Long:  "Enable manual sync for a sync window. Requires ID which can be found by running \"argocd proj windows list PROJECT\"",
   101  		Example: `
   102  #Enabling manual sync for a general case
   103  argocd proj windows enable-manual-sync PROJECT ID
   104  
   105  #Enabling manual sync for a windows set on the default project with Id 2
   106  argocd proj windows enable-manual-sync default 2
   107  
   108  #Enabling manual sync with a custom message
   109  argocd proj windows enable-manual-sync my-app-project --message "Manual sync initiated by admin`,
   110  		Run: func(c *cobra.Command, args []string) {
   111  			ctx := c.Context()
   112  
   113  			if len(args) != 2 {
   114  				c.HelpFunc()(c, args)
   115  				os.Exit(1)
   116  			}
   117  
   118  			projName := args[0]
   119  			id, err := strconv.Atoi(args[1])
   120  			errors.CheckError(err)
   121  
   122  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   123  			defer utilio.Close(conn)
   124  
   125  			proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
   126  			errors.CheckError(err)
   127  
   128  			for i, window := range proj.Spec.SyncWindows {
   129  				if id == i {
   130  					window.ManualSync = true
   131  				}
   132  			}
   133  
   134  			_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
   135  			errors.CheckError(err)
   136  		},
   137  	}
   138  	return command
   139  }
   140  
   141  // NewProjectWindowsAddWindowCommand returns a new instance of an `argocd proj windows add` command
   142  func NewProjectWindowsAddWindowCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   143  	var (
   144  		kind         string
   145  		schedule     string
   146  		duration     string
   147  		applications []string
   148  		namespaces   []string
   149  		clusters     []string
   150  		manualSync   bool
   151  		timeZone     string
   152  		andOperator  bool
   153  		description  string
   154  	)
   155  	command := &cobra.Command{
   156  		Use:   "add PROJECT",
   157  		Short: "Add a sync window to a project",
   158  		Example: `
   159  #Add a 1 hour allow sync window
   160  argocd proj windows add PROJECT \
   161      --kind allow \
   162      --schedule "0 22 * * *" \
   163      --duration 1h \
   164      --applications "*" \
   165      --description "Ticket 123"
   166  
   167  #Add a deny sync window with the ability to manually sync.
   168  argocd proj windows add PROJECT \
   169      --kind deny \
   170      --schedule "30 10 * * *" \
   171      --duration 30m \
   172      --applications "prod-\\*,website" \
   173      --namespaces "default,\\*-prod" \
   174      --clusters "prod,staging" \
   175      --manual-sync \
   176      --description "Ticket 123"
   177  	`,
   178  		Run: func(c *cobra.Command, args []string) {
   179  			ctx := c.Context()
   180  
   181  			if len(args) != 1 {
   182  				c.HelpFunc()(c, args)
   183  				os.Exit(1)
   184  			}
   185  			projName := args[0]
   186  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   187  			defer utilio.Close(conn)
   188  
   189  			proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
   190  			errors.CheckError(err)
   191  
   192  			err = proj.Spec.AddWindow(kind, schedule, duration, applications, namespaces, clusters, manualSync, timeZone, andOperator, description)
   193  			errors.CheckError(err)
   194  
   195  			_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
   196  			errors.CheckError(err)
   197  		},
   198  	}
   199  	command.Flags().StringVarP(&kind, "kind", "k", "", "Sync window kind, either allow or deny")
   200  	command.Flags().StringVar(&schedule, "schedule", "", "Sync window schedule in cron format. (e.g. --schedule \"0 22 * * *\")")
   201  	command.Flags().StringVar(&duration, "duration", "", "Sync window duration. (e.g. --duration 1h)")
   202  	command.Flags().StringSliceVar(&applications, "applications", []string{}, "Applications that the schedule will be applied to. Comma separated, wildcards supported (e.g. --applications prod-\\*,website)")
   203  	command.Flags().StringSliceVar(&namespaces, "namespaces", []string{}, "Namespaces that the schedule will be applied to. Comma separated, wildcards supported (e.g. --namespaces default,\\*-prod)")
   204  	command.Flags().StringSliceVar(&clusters, "clusters", []string{}, "Clusters that the schedule will be applied to. Comma separated, wildcards supported (e.g. --clusters prod,staging)")
   205  	command.Flags().BoolVar(&manualSync, "manual-sync", false, "Allow manual syncs for both deny and allow windows")
   206  	command.Flags().StringVar(&timeZone, "time-zone", "UTC", "Time zone of the sync window")
   207  	command.Flags().BoolVar(&andOperator, "use-and-operator", false, "Use AND operator for matching applications, namespaces and clusters instead of the default OR operator")
   208  	command.Flags().StringVar(&description, "description", "", `Sync window description`)
   209  
   210  	return command
   211  }
   212  
   213  // NewProjectWindowsDeleteCommand returns a new instance of an `argocd proj windows delete` command
   214  func NewProjectWindowsDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   215  	command := &cobra.Command{
   216  		Use:   "delete PROJECT ID",
   217  		Short: "Delete a sync window from a project. Requires ID which can be found by running \"argocd proj windows list PROJECT\"",
   218  		Example: `
   219  #Delete a sync window from a project (default) with ID 0
   220  argocd proj windows delete default 0
   221  
   222  #Delete a sync window from a project (new-project) with ID 1
   223  argocd proj windows delete new-project 1`,
   224  		Run: func(c *cobra.Command, args []string) {
   225  			ctx := c.Context()
   226  
   227  			if len(args) != 2 {
   228  				c.HelpFunc()(c, args)
   229  				os.Exit(1)
   230  			}
   231  
   232  			projName := args[0]
   233  			id, err := strconv.Atoi(args[1])
   234  			errors.CheckError(err)
   235  
   236  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   237  			defer utilio.Close(conn)
   238  
   239  			proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
   240  			errors.CheckError(err)
   241  
   242  			err = proj.Spec.DeleteWindow(id)
   243  			errors.CheckError(err)
   244  
   245  			promptUtil := utils.NewPrompt(clientOpts.PromptsEnabled)
   246  			canDelete := promptUtil.Confirm("Are you sure you want to delete sync window? [y/n]")
   247  			if canDelete {
   248  				_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
   249  				errors.CheckError(err)
   250  			} else {
   251  				fmt.Printf("The command to delete the sync window was cancelled\n")
   252  			}
   253  		},
   254  	}
   255  	return command
   256  }
   257  
   258  // NewProjectWindowsUpdateCommand returns a new instance of an `argocd proj windows update` command
   259  func NewProjectWindowsUpdateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   260  	var (
   261  		schedule     string
   262  		duration     string
   263  		applications []string
   264  		namespaces   []string
   265  		clusters     []string
   266  		timeZone     string
   267  		description  string
   268  	)
   269  	command := &cobra.Command{
   270  		Use:   "update PROJECT ID",
   271  		Short: "Update a project sync window",
   272  		Long:  "Update a project sync window. Requires ID which can be found by running \"argocd proj windows list PROJECT\"",
   273  		Example: `# Change a sync window's schedule
   274  argocd proj windows update PROJECT ID \
   275      --schedule "0 20 * * *"
   276  `,
   277  		Run: func(c *cobra.Command, args []string) {
   278  			ctx := c.Context()
   279  
   280  			if len(args) != 2 {
   281  				c.HelpFunc()(c, args)
   282  				os.Exit(1)
   283  			}
   284  
   285  			projName := args[0]
   286  			id, err := strconv.Atoi(args[1])
   287  			errors.CheckError(err)
   288  
   289  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   290  			defer utilio.Close(conn)
   291  
   292  			proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
   293  			errors.CheckError(err)
   294  
   295  			for i, window := range proj.Spec.SyncWindows {
   296  				if id == i {
   297  					err := window.Update(schedule, duration, applications, namespaces, clusters, timeZone, description)
   298  					if err != nil {
   299  						errors.CheckError(err)
   300  					}
   301  				}
   302  			}
   303  
   304  			_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
   305  			errors.CheckError(err)
   306  		},
   307  	}
   308  	command.Flags().StringVar(&schedule, "schedule", "", "Sync window schedule in cron format. (e.g. --schedule \"0 22 * * *\")")
   309  	command.Flags().StringVar(&duration, "duration", "", "Sync window duration. (e.g. --duration 1h)")
   310  	command.Flags().StringSliceVar(&applications, "applications", []string{}, "Applications that the schedule will be applied to. Comma separated, wildcards supported (e.g. --applications prod-\\*,website)")
   311  	command.Flags().StringSliceVar(&namespaces, "namespaces", []string{}, "Namespaces that the schedule will be applied to. Comma separated, wildcards supported (e.g. --namespaces default,\\*-prod)")
   312  	command.Flags().StringSliceVar(&clusters, "clusters", []string{}, "Clusters that the schedule will be applied to. Comma separated, wildcards supported (e.g. --clusters prod,staging)")
   313  	command.Flags().StringVar(&timeZone, "time-zone", "UTC", "Time zone of the sync window. (e.g. --time-zone \"America/New_York\")")
   314  	command.Flags().StringVar(&description, "description", "", "Sync window description")
   315  	return command
   316  }
   317  
   318  // NewProjectWindowsListCommand returns a new instance of an `argocd proj windows list` command
   319  func NewProjectWindowsListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
   320  	var output string
   321  	command := &cobra.Command{
   322  		Use:   "list PROJECT",
   323  		Short: "List project sync windows",
   324  		Example: `
   325  #List project windows
   326  argocd proj windows list PROJECT
   327  
   328  #List project windows in yaml format
   329  argocd proj windows list PROJECT -o yaml
   330  
   331  #List project windows info for a project name (test-project)
   332  argocd proj windows list test-project`,
   333  		Run: func(c *cobra.Command, args []string) {
   334  			ctx := c.Context()
   335  
   336  			if len(args) != 1 {
   337  				c.HelpFunc()(c, args)
   338  				os.Exit(1)
   339  			}
   340  			projName := args[0]
   341  			conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
   342  			defer utilio.Close(conn)
   343  
   344  			proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
   345  			errors.CheckError(err)
   346  			switch output {
   347  			case "yaml", "json":
   348  				err := PrintResourceList(proj.Spec.SyncWindows, output, false)
   349  				errors.CheckError(err)
   350  			case "wide", "":
   351  				printSyncWindows(proj)
   352  			default:
   353  				errors.CheckError(fmt.Errorf("unknown output format: %s", output))
   354  			}
   355  		},
   356  	}
   357  	command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide")
   358  	return command
   359  }
   360  
   361  // Print table of sync window data
   362  func printSyncWindows(proj *v1alpha1.AppProject) {
   363  	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
   364  	var fmtStr string
   365  	headers := []any{"ID", "STATUS", "KIND", "SCHEDULE", "DURATION", "APPLICATIONS", "NAMESPACES", "CLUSTERS", "MANUALSYNC", "TIMEZONE"}
   366  	fmtStr = strings.Repeat("%s\t", len(headers)) + "\n"
   367  	fmt.Fprintf(w, fmtStr, headers...)
   368  	if proj.Spec.SyncWindows.HasWindows() {
   369  		for i, window := range proj.Spec.SyncWindows {
   370  			isActive, _ := window.Active()
   371  			vals := []any{
   372  				strconv.Itoa(i),
   373  				formatBoolOutput(isActive),
   374  				window.Kind,
   375  				window.Schedule,
   376  				window.Duration,
   377  				formatListOutput(window.Applications),
   378  				formatListOutput(window.Namespaces),
   379  				formatListOutput(window.Clusters),
   380  				formatBoolEnabledOutput(window.ManualSync),
   381  				window.TimeZone,
   382  				formatBoolEnabledOutput(window.UseAndOperator),
   383  			}
   384  			fmt.Fprintf(w, fmtStr, vals...)
   385  		}
   386  	}
   387  	_ = w.Flush()
   388  }
   389  
   390  func formatListOutput(list []string) string {
   391  	var o string
   392  	if len(list) == 0 {
   393  		o = "-"
   394  	} else {
   395  		o = strings.Join(list, ",")
   396  	}
   397  	return o
   398  }
   399  
   400  func formatBoolOutput(active bool) string {
   401  	var o string
   402  	if active {
   403  		o = "Active"
   404  	} else {
   405  		o = "Inactive"
   406  	}
   407  	return o
   408  }
   409  
   410  func formatBoolEnabledOutput(active bool) string {
   411  	var o string
   412  	if active {
   413  		o = "Enabled"
   414  	} else {
   415  		o = "Disabled"
   416  	}
   417  	return o
   418  }