mirror of
https://github.com/hanzoai/deploy.git
synced 2026-10-07 08:44:50 +00:00
go.mod still declared `module github.com/hanzoai/deploy` after the repo was renamed. A Go module path is not cosmetic — it is the resolvable location. So `go get github.com/hanzoai/cd` could not work, and anything importing this module had to spell a name that no longer exists at that address. The build log of v3.6.0 is where it surfaced: the compiler was still emitting github.com/hanzoai/deploy/cmd/cd/commands. 3541 import paths across 690 .go, 19 .proto, 4 .yaml, 3 .sh, 2 .mod, Procfile and Makefile. Plus the 62 ghcr.io/hanzoai/deploy image references, moved to ghcr.io/hanzoai/cd to match hanzo.yml's build target — a distinct string from the module path, replaced separately so neither could clobber the other. go mod tidy clean, CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./... clean. Co-authored-by: Hanzo Dev <[email protected]>
457 lines
16 KiB
Go
457 lines
16 KiB
Go
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/hanzoai/cd/cmd/cd/commands/headless"
|
|
"github.com/hanzoai/cd/cmd/cd/commands/utils"
|
|
cdclient "github.com/hanzoai/cd/pkg/apiclient"
|
|
projectpkg "github.com/hanzoai/cd/pkg/apiclient/project"
|
|
"github.com/hanzoai/cd/pkg/apis/application/v1alpha1"
|
|
"github.com/hanzoai/cd/util/errors"
|
|
utilio "github.com/hanzoai/cd/util/io"
|
|
)
|
|
|
|
// NewProjectWindowsCommand returns a new instance of the `cd proj windows` command
|
|
func NewProjectWindowsCommand(clientOpts *cdclient.ClientOptions) *cobra.Command {
|
|
roleCommand := &cobra.Command{
|
|
Use: "windows",
|
|
Short: "Manage a project's sync windows",
|
|
Example: `
|
|
#Add a sync window to a project
|
|
cd proj windows add my-project \
|
|
--schedule "0 0 * * 1-5" \
|
|
--duration 3600 \
|
|
--prune
|
|
|
|
#Delete a sync window from a project
|
|
cd proj windows delete <project-name> <window-id>
|
|
|
|
#List project sync windows
|
|
cd 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 *cdclient.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 `cd proj windows disable-manual-sync` command
|
|
func NewProjectWindowsDisableManualSyncCommand(clientOpts *cdclient.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 \"cd proj windows list PROJECT\"",
|
|
`
|
|
#Disable manual sync for a sync window for the Project
|
|
cd proj windows disable-manual-sync PROJECT ID
|
|
|
|
#Disabling manual sync for a windows set on the default project with Id 0
|
|
cd proj windows disable-manual-sync default 0`,
|
|
func(window *v1alpha1.SyncWindow) {
|
|
window.ManualSync = false
|
|
},
|
|
)
|
|
}
|
|
|
|
// NewProjectWindowsEnableManualSyncCommand returns a new instance of an `cd proj windows enable-manual-sync` command
|
|
func NewProjectWindowsEnableManualSyncCommand(clientOpts *cdclient.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 \"cd proj windows list PROJECT\"",
|
|
`
|
|
#Enabling manual sync for a general case
|
|
cd proj windows enable-manual-sync PROJECT ID
|
|
|
|
#Enabling manual sync for a windows set on the default project with Id 2
|
|
cd proj windows enable-manual-sync default 2
|
|
|
|
#Enabling manual sync with a custom message
|
|
cd 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 `cd proj windows disable-sync-overrun` command
|
|
func NewProjectWindowsDisableSyncOverrunCommand(clientOpts *cdclient.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 \"cd proj windows list PROJECT\"",
|
|
`
|
|
#Disable sync overrun for a sync window for the Project
|
|
cd proj windows disable-sync-overrun PROJECT ID
|
|
|
|
#Disabling sync overrun for a window set on the default project with Id 0
|
|
cd proj windows disable-sync-overrun default 0`,
|
|
func(window *v1alpha1.SyncWindow) {
|
|
window.SyncOverrun = false
|
|
},
|
|
)
|
|
}
|
|
|
|
// NewProjectWindowsEnableSyncOverrunCommand returns a new instance of an `cd proj windows enable-sync-overrun` command
|
|
func NewProjectWindowsEnableSyncOverrunCommand(clientOpts *cdclient.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 \"cd proj windows list PROJECT\"",
|
|
`
|
|
#Enable sync overrun for a sync window
|
|
cd proj windows enable-sync-overrun PROJECT ID
|
|
|
|
#Enabling sync overrun for a window set on the default project with Id 2
|
|
cd proj windows enable-sync-overrun default 2`,
|
|
func(window *v1alpha1.SyncWindow) {
|
|
window.SyncOverrun = true
|
|
},
|
|
)
|
|
}
|
|
|
|
// NewProjectWindowsAddWindowCommand returns a new instance of an `cd proj windows add` command
|
|
func NewProjectWindowsAddWindowCommand(clientOpts *cdclient.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
|
|
cd 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.
|
|
cd 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 `cd proj windows delete` command
|
|
func NewProjectWindowsDeleteCommand(clientOpts *cdclient.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 \"cd proj windows list PROJECT\"",
|
|
Example: `
|
|
#Delete a sync window from a project (default) with ID 0
|
|
cd proj windows delete default 0
|
|
|
|
#Delete a sync window from a project (new-project) with ID 1
|
|
cd 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 `cd proj windows update` command
|
|
func NewProjectWindowsUpdateCommand(clientOpts *cdclient.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 \"cd proj windows list PROJECT\"",
|
|
Example: `# Change a sync window's schedule
|
|
cd 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 `cd proj windows list` command
|
|
func NewProjectWindowsListCommand(clientOpts *cdclient.ClientOptions) *cobra.Command {
|
|
var output string
|
|
command := &cobra.Command{
|
|
Use: "list PROJECT",
|
|
Short: "List project sync windows",
|
|
Example: `
|
|
#List project windows
|
|
cd proj windows list PROJECT
|
|
|
|
#List project windows in yaml format
|
|
cd proj windows list PROJECT -o yaml
|
|
|
|
#List project windows info for a project name (test-project)
|
|
cd 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
|
|
}
|