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 }