mirror of
https://github.com/argoproj/argo-cd
synced 2026-04-21 17:07:16 +00:00
Some checks are pending
Integration tests / Run end-to-end tests (push) Blocked by required conditions
Integration tests / E2E Tests - Composite result (push) Blocked by required conditions
Integration tests / changes (push) Waiting to run
Integration tests / Ensure Go modules synchronicity (push) Blocked by required conditions
Integration tests / Build & cache Go code (push) Blocked by required conditions
Integration tests / Lint Go code (push) Blocked by required conditions
Integration tests / Run unit tests for Go packages (push) Blocked by required conditions
Integration tests / Run unit tests with -race for Go packages (push) Blocked by required conditions
Integration tests / Check changes to generated code (push) Blocked by required conditions
Integration tests / Build, test & lint UI code (push) Blocked by required conditions
Integration tests / shellcheck (push) Waiting to run
Integration tests / Process & analyze test artifacts (push) Blocked by required conditions
Code scanning - action / CodeQL-Build (push) Waiting to run
Image / set-vars (push) Waiting to run
Image / build-only (push) Blocked by required conditions
Image / build-and-publish (push) Blocked by required conditions
Image / build-and-publish-provenance (push) Blocked by required conditions
Image / Deploy (push) Blocked by required conditions
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
Signed-off-by: Vilius Puškunalis <47086537+puskunalis@users.noreply.github.com>
456 lines
16 KiB
Go
456 lines
16 KiB
Go
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/headless"
|
|
"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/utils"
|
|
argocdclient "github.com/argoproj/argo-cd/v3/pkg/apiclient"
|
|
projectpkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/project"
|
|
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
|
"github.com/argoproj/argo-cd/v3/util/errors"
|
|
utilio "github.com/argoproj/argo-cd/v3/util/io"
|
|
)
|
|
|
|
// NewProjectWindowsCommand returns a new instance of the `argocd proj windows` command
|
|
func NewProjectWindowsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
|
roleCommand := &cobra.Command{
|
|
Use: "windows",
|
|
Short: "Manage a project's sync windows",
|
|
Example: `
|
|
#Add a sync window to a project
|
|
argocd proj windows add my-project \
|
|
--schedule "0 0 * * 1-5" \
|
|
--duration 3600 \
|
|
--prune
|
|
|
|
#Delete a sync window from a project
|
|
argocd proj windows delete <project-name> <window-id>
|
|
|
|
#List project sync windows
|
|
argocd proj windows list <project-name>`,
|
|
Run: func(c *cobra.Command, args []string) {
|
|
c.HelpFunc()(c, args)
|
|
os.Exit(1)
|
|
},
|
|
}
|
|
roleCommand.AddCommand(NewProjectWindowsDisableManualSyncCommand(clientOpts))
|
|
roleCommand.AddCommand(NewProjectWindowsEnableManualSyncCommand(clientOpts))
|
|
roleCommand.AddCommand(NewProjectWindowsDisableSyncOverrunCommand(clientOpts))
|
|
roleCommand.AddCommand(NewProjectWindowsEnableSyncOverrunCommand(clientOpts))
|
|
roleCommand.AddCommand(NewProjectWindowsAddWindowCommand(clientOpts))
|
|
roleCommand.AddCommand(NewProjectWindowsDeleteCommand(clientOpts))
|
|
roleCommand.AddCommand(NewProjectWindowsListCommand(clientOpts))
|
|
roleCommand.AddCommand(NewProjectWindowsUpdateCommand(clientOpts))
|
|
return roleCommand
|
|
}
|
|
|
|
// newProjectWindowsToggleCommand creates a command for toggling a boolean field on a sync window
|
|
func newProjectWindowsToggleCommand(clientOpts *argocdclient.ClientOptions, use, short, long, example string, updateFn func(*v1alpha1.SyncWindow)) *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: use,
|
|
Short: short,
|
|
Long: long,
|
|
Example: example,
|
|
Run: func(c *cobra.Command, args []string) {
|
|
ctx := c.Context()
|
|
|
|
if len(args) != 2 {
|
|
c.HelpFunc()(c, args)
|
|
os.Exit(1)
|
|
}
|
|
|
|
projName := args[0]
|
|
id, err := strconv.Atoi(args[1])
|
|
errors.CheckError(err)
|
|
|
|
conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
|
|
defer utilio.Close(conn)
|
|
|
|
proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
|
|
errors.CheckError(err)
|
|
|
|
found := false
|
|
for i, window := range proj.Spec.SyncWindows {
|
|
if id == i {
|
|
updateFn(window)
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
errors.CheckError(fmt.Errorf("window with id '%d' not found", id))
|
|
}
|
|
|
|
_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
|
|
errors.CheckError(err)
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewProjectWindowsDisableManualSyncCommand returns a new instance of an `argocd proj windows disable-manual-sync` command
|
|
func NewProjectWindowsDisableManualSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
|
return newProjectWindowsToggleCommand(
|
|
clientOpts,
|
|
"disable-manual-sync PROJECT ID",
|
|
"Disable manual sync for a sync window",
|
|
"Disable manual sync for a sync window. Requires ID which can be found by running \"argocd proj windows list PROJECT\"",
|
|
`
|
|
#Disable manual sync for a sync window for the Project
|
|
argocd proj windows disable-manual-sync PROJECT ID
|
|
|
|
#Disabling manual sync for a windows set on the default project with Id 0
|
|
argocd proj windows disable-manual-sync default 0`,
|
|
func(window *v1alpha1.SyncWindow) {
|
|
window.ManualSync = false
|
|
},
|
|
)
|
|
}
|
|
|
|
// NewProjectWindowsEnableManualSyncCommand returns a new instance of an `argocd proj windows enable-manual-sync` command
|
|
func NewProjectWindowsEnableManualSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
|
return newProjectWindowsToggleCommand(
|
|
clientOpts,
|
|
"enable-manual-sync PROJECT ID",
|
|
"Enable manual sync for a sync window",
|
|
"Enable manual sync for a sync window. Requires ID which can be found by running \"argocd proj windows list PROJECT\"",
|
|
`
|
|
#Enabling manual sync for a general case
|
|
argocd proj windows enable-manual-sync PROJECT ID
|
|
|
|
#Enabling manual sync for a windows set on the default project with Id 2
|
|
argocd proj windows enable-manual-sync default 2
|
|
|
|
#Enabling manual sync with a custom message
|
|
argocd proj windows enable-manual-sync my-app-project --message "Manual sync initiated by admin`,
|
|
func(window *v1alpha1.SyncWindow) {
|
|
window.ManualSync = true
|
|
},
|
|
)
|
|
}
|
|
|
|
// NewProjectWindowsDisableSyncOverrunCommand returns a new instance of an `argocd proj windows disable-sync-overrun` command
|
|
func NewProjectWindowsDisableSyncOverrunCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
|
return newProjectWindowsToggleCommand(
|
|
clientOpts,
|
|
"disable-sync-overrun PROJECT ID",
|
|
"Disable sync overrun for a sync window",
|
|
"Disable sync overrun for a sync window. Requires ID which can be found by running \"argocd proj windows list PROJECT\"",
|
|
`
|
|
#Disable sync overrun for a sync window for the Project
|
|
argocd proj windows disable-sync-overrun PROJECT ID
|
|
|
|
#Disabling sync overrun for a window set on the default project with Id 0
|
|
argocd proj windows disable-sync-overrun default 0`,
|
|
func(window *v1alpha1.SyncWindow) {
|
|
window.SyncOverrun = false
|
|
},
|
|
)
|
|
}
|
|
|
|
// NewProjectWindowsEnableSyncOverrunCommand returns a new instance of an `argocd proj windows enable-sync-overrun` command
|
|
func NewProjectWindowsEnableSyncOverrunCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
|
return newProjectWindowsToggleCommand(
|
|
clientOpts,
|
|
"enable-sync-overrun PROJECT ID",
|
|
"Enable sync overrun for a sync window",
|
|
"Enable sync overrun for a sync window. When enabled on a deny window, syncs that started before the deny window will be allowed to continue. When enabled on an allow window, syncs that started during the allow window can continue after the window ends. Requires ID which can be found by running \"argocd proj windows list PROJECT\"",
|
|
`
|
|
#Enable sync overrun for a sync window
|
|
argocd proj windows enable-sync-overrun PROJECT ID
|
|
|
|
#Enabling sync overrun for a window set on the default project with Id 2
|
|
argocd proj windows enable-sync-overrun default 2`,
|
|
func(window *v1alpha1.SyncWindow) {
|
|
window.SyncOverrun = true
|
|
},
|
|
)
|
|
}
|
|
|
|
// NewProjectWindowsAddWindowCommand returns a new instance of an `argocd proj windows add` command
|
|
func NewProjectWindowsAddWindowCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
|
var (
|
|
kind string
|
|
schedule string
|
|
duration string
|
|
applications []string
|
|
namespaces []string
|
|
clusters []string
|
|
manualSync bool
|
|
syncOverrun bool
|
|
timeZone string
|
|
andOperator bool
|
|
description string
|
|
)
|
|
command := &cobra.Command{
|
|
Use: "add PROJECT",
|
|
Short: "Add a sync window to a project",
|
|
Example: `
|
|
#Add a 1 hour allow sync window
|
|
argocd proj windows add PROJECT \
|
|
--kind allow \
|
|
--schedule "0 22 * * *" \
|
|
--duration 1h \
|
|
--applications "*" \
|
|
--description "Ticket 123"
|
|
|
|
#Add a deny sync window with the ability to manually sync and sync overrun.
|
|
argocd proj windows add PROJECT \
|
|
--kind deny \
|
|
--schedule "30 10 * * *" \
|
|
--duration 30m \
|
|
--applications "prod-\\*,website" \
|
|
--namespaces "default,\\*-prod" \
|
|
--clusters "prod,staging" \
|
|
--manual-sync \
|
|
--sync-overrun \
|
|
--description "Ticket 123"`,
|
|
Run: func(c *cobra.Command, args []string) {
|
|
ctx := c.Context()
|
|
|
|
if len(args) != 1 {
|
|
c.HelpFunc()(c, args)
|
|
os.Exit(1)
|
|
}
|
|
projName := args[0]
|
|
conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
|
|
defer utilio.Close(conn)
|
|
|
|
proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
|
|
errors.CheckError(err)
|
|
|
|
err = proj.Spec.AddWindow(kind, schedule, duration, applications, namespaces, clusters, manualSync, timeZone, andOperator, description, syncOverrun)
|
|
errors.CheckError(err)
|
|
|
|
_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
|
|
errors.CheckError(err)
|
|
},
|
|
}
|
|
command.Flags().StringVarP(&kind, "kind", "k", "", "Sync window kind, either allow or deny")
|
|
command.Flags().StringVar(&schedule, "schedule", "", "Sync window schedule in cron format. (e.g. --schedule \"0 22 * * *\")")
|
|
command.Flags().StringVar(&duration, "duration", "", "Sync window duration. (e.g. --duration 1h)")
|
|
command.Flags().StringSliceVar(&applications, "applications", []string{}, "Applications that the schedule will be applied to. Comma separated, wildcards supported (e.g. --applications prod-\\*,website)")
|
|
command.Flags().StringSliceVar(&namespaces, "namespaces", []string{}, "Namespaces that the schedule will be applied to. Comma separated, wildcards supported (e.g. --namespaces default,\\*-prod)")
|
|
command.Flags().StringSliceVar(&clusters, "clusters", []string{}, "Clusters that the schedule will be applied to. Comma separated, wildcards supported (e.g. --clusters prod,staging)")
|
|
command.Flags().BoolVar(&manualSync, "manual-sync", false, "Allow manual syncs for both deny and allow windows")
|
|
command.Flags().BoolVar(&syncOverrun, "sync-overrun", false, "Allow syncs to continue: for deny windows, syncs that started before the window; for allow windows, syncs that started during the window")
|
|
command.Flags().StringVar(&timeZone, "time-zone", "UTC", "Time zone of the sync window")
|
|
command.Flags().BoolVar(&andOperator, "use-and-operator", false, "Use AND operator for matching applications, namespaces and clusters instead of the default OR operator")
|
|
command.Flags().StringVar(&description, "description", "", `Sync window description`)
|
|
|
|
return command
|
|
}
|
|
|
|
// NewProjectWindowsDeleteCommand returns a new instance of an `argocd proj windows delete` command
|
|
func NewProjectWindowsDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
|
command := &cobra.Command{
|
|
Use: "delete PROJECT ID",
|
|
Short: "Delete a sync window from a project. Requires ID which can be found by running \"argocd proj windows list PROJECT\"",
|
|
Example: `
|
|
#Delete a sync window from a project (default) with ID 0
|
|
argocd proj windows delete default 0
|
|
|
|
#Delete a sync window from a project (new-project) with ID 1
|
|
argocd proj windows delete new-project 1`,
|
|
Run: func(c *cobra.Command, args []string) {
|
|
ctx := c.Context()
|
|
|
|
if len(args) != 2 {
|
|
c.HelpFunc()(c, args)
|
|
os.Exit(1)
|
|
}
|
|
|
|
projName := args[0]
|
|
id, err := strconv.Atoi(args[1])
|
|
errors.CheckError(err)
|
|
|
|
conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
|
|
defer utilio.Close(conn)
|
|
|
|
proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
|
|
errors.CheckError(err)
|
|
|
|
err = proj.Spec.DeleteWindow(id)
|
|
errors.CheckError(err)
|
|
|
|
promptUtil := utils.NewPrompt(clientOpts.PromptsEnabled)
|
|
canDelete := promptUtil.Confirm("Are you sure you want to delete sync window? [y/n]")
|
|
if canDelete {
|
|
_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
|
|
errors.CheckError(err)
|
|
} else {
|
|
fmt.Print("The command to delete the sync window was cancelled\n")
|
|
}
|
|
},
|
|
}
|
|
return command
|
|
}
|
|
|
|
// NewProjectWindowsUpdateCommand returns a new instance of an `argocd proj windows update` command
|
|
func NewProjectWindowsUpdateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
|
var (
|
|
schedule string
|
|
duration string
|
|
applications []string
|
|
namespaces []string
|
|
clusters []string
|
|
timeZone string
|
|
description string
|
|
)
|
|
command := &cobra.Command{
|
|
Use: "update PROJECT ID",
|
|
Short: "Update a project sync window",
|
|
Long: "Update a project sync window. Requires ID which can be found by running \"argocd proj windows list PROJECT\"",
|
|
Example: `# Change a sync window's schedule
|
|
argocd proj windows update PROJECT ID \
|
|
--schedule "0 20 * * *"
|
|
`,
|
|
Run: func(c *cobra.Command, args []string) {
|
|
ctx := c.Context()
|
|
|
|
if len(args) != 2 {
|
|
c.HelpFunc()(c, args)
|
|
os.Exit(1)
|
|
}
|
|
|
|
projName := args[0]
|
|
id, err := strconv.Atoi(args[1])
|
|
errors.CheckError(err)
|
|
|
|
conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
|
|
defer utilio.Close(conn)
|
|
|
|
proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
|
|
errors.CheckError(err)
|
|
|
|
for i, window := range proj.Spec.SyncWindows {
|
|
if id == i {
|
|
err := window.Update(schedule, duration, applications, namespaces, clusters, timeZone, description)
|
|
if err != nil {
|
|
errors.CheckError(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
_, err = projIf.Update(ctx, &projectpkg.ProjectUpdateRequest{Project: proj})
|
|
errors.CheckError(err)
|
|
},
|
|
}
|
|
command.Flags().StringVar(&schedule, "schedule", "", "Sync window schedule in cron format. (e.g. --schedule \"0 22 * * *\")")
|
|
command.Flags().StringVar(&duration, "duration", "", "Sync window duration. (e.g. --duration 1h)")
|
|
command.Flags().StringSliceVar(&applications, "applications", []string{}, "Applications that the schedule will be applied to. Comma separated, wildcards supported (e.g. --applications prod-\\*,website)")
|
|
command.Flags().StringSliceVar(&namespaces, "namespaces", []string{}, "Namespaces that the schedule will be applied to. Comma separated, wildcards supported (e.g. --namespaces default,\\*-prod)")
|
|
command.Flags().StringSliceVar(&clusters, "clusters", []string{}, "Clusters that the schedule will be applied to. Comma separated, wildcards supported (e.g. --clusters prod,staging)")
|
|
command.Flags().StringVar(&timeZone, "time-zone", "UTC", "Time zone of the sync window. (e.g. --time-zone \"America/New_York\")")
|
|
command.Flags().StringVar(&description, "description", "", "Sync window description")
|
|
return command
|
|
}
|
|
|
|
// NewProjectWindowsListCommand returns a new instance of an `argocd proj windows list` command
|
|
func NewProjectWindowsListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
|
var output string
|
|
command := &cobra.Command{
|
|
Use: "list PROJECT",
|
|
Short: "List project sync windows",
|
|
Example: `
|
|
#List project windows
|
|
argocd proj windows list PROJECT
|
|
|
|
#List project windows in yaml format
|
|
argocd proj windows list PROJECT -o yaml
|
|
|
|
#List project windows info for a project name (test-project)
|
|
argocd proj windows list test-project`,
|
|
Run: func(c *cobra.Command, args []string) {
|
|
ctx := c.Context()
|
|
|
|
if len(args) != 1 {
|
|
c.HelpFunc()(c, args)
|
|
os.Exit(1)
|
|
}
|
|
projName := args[0]
|
|
conn, projIf := headless.NewClientOrDie(clientOpts, c).NewProjectClientOrDie()
|
|
defer utilio.Close(conn)
|
|
|
|
proj, err := projIf.Get(ctx, &projectpkg.ProjectQuery{Name: projName})
|
|
errors.CheckError(err)
|
|
switch output {
|
|
case "yaml", "json":
|
|
err := PrintResourceList(proj.Spec.SyncWindows, output, false)
|
|
errors.CheckError(err)
|
|
case "wide", "":
|
|
printSyncWindows(proj)
|
|
default:
|
|
errors.CheckError(fmt.Errorf("unknown output format: %s", output))
|
|
}
|
|
},
|
|
}
|
|
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide")
|
|
return command
|
|
}
|
|
|
|
// Print table of sync window data
|
|
func printSyncWindows(proj *v1alpha1.AppProject) {
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
var fmtStr string
|
|
headers := []any{"ID", "STATUS", "KIND", "SCHEDULE", "DURATION", "APPLICATIONS", "NAMESPACES", "CLUSTERS", "MANUALSYNC", "SYNCOVERRUN", "TIMEZONE", "USEANDOPERATOR"}
|
|
fmtStr = strings.Repeat("%s\t", len(headers)) + "\n"
|
|
fmt.Fprintf(w, fmtStr, headers...)
|
|
if proj.Spec.SyncWindows.HasWindows() {
|
|
for i, window := range proj.Spec.SyncWindows {
|
|
isActive, _ := window.Active()
|
|
vals := []any{
|
|
strconv.Itoa(i),
|
|
formatBoolOutput(isActive),
|
|
window.Kind,
|
|
window.Schedule,
|
|
window.Duration,
|
|
formatListOutput(window.Applications),
|
|
formatListOutput(window.Namespaces),
|
|
formatListOutput(window.Clusters),
|
|
formatBoolEnabledOutput(window.ManualSync),
|
|
formatBoolEnabledOutput(window.SyncOverrun),
|
|
window.TimeZone,
|
|
formatBoolEnabledOutput(window.UseAndOperator),
|
|
}
|
|
fmt.Fprintf(w, fmtStr, vals...)
|
|
}
|
|
}
|
|
_ = w.Flush()
|
|
}
|
|
|
|
func formatListOutput(list []string) string {
|
|
var o string
|
|
if len(list) == 0 {
|
|
o = "-"
|
|
} else {
|
|
o = strings.Join(list, ",")
|
|
}
|
|
return o
|
|
}
|
|
|
|
func formatBoolOutput(active bool) string {
|
|
var o string
|
|
if active {
|
|
o = "Active"
|
|
} else {
|
|
o = "Inactive"
|
|
}
|
|
return o
|
|
}
|
|
|
|
func formatBoolEnabledOutput(active bool) string {
|
|
var o string
|
|
if active {
|
|
o = "Enabled"
|
|
} else {
|
|
o = "Disabled"
|
|
}
|
|
return o
|
|
}
|