package modifyclusterconfig import ( "context" "fmt" "regexp" "strings" "time" "edge-infra.dev/pkg/edge/api/graph/model" "edge-infra.dev/pkg/edge/api/utils" "edge-infra.dev/pkg/edge/edgeadmin/edgecliutils" "edge-infra.dev/pkg/edge/edgecli" "edge-infra.dev/pkg/edge/edgecli/constructors" "edge-infra.dev/pkg/edge/edgecli/flagutil" "edge-infra.dev/pkg/lib/cli/command" "edge-infra.dev/pkg/lib/cli/rags" ) func NewCmd(cfg *edgecli.Config) *command.Command { var cmd *command.Command flagsets := flagutil.GetConnectionFlags(cfg) var storeName, bannerName string config := model.UpdateClusterConfig{} cmd = &command.Command{ ShortUsage: "edge clusterconfig modify", ShortHelp: "Update cluster config", Flags: append( flagsets, &rags.Rag{ Name: flagutil.StoreFlag, Usage: "Name of the Store", Value: &rags.String{Var: &storeName}, Required: true, }, &rags.Rag{ Name: flagutil.BannerFlag, Usage: "Banner name", Value: &rags.String{Var: &bannerName}, Required: true, }, &rags.Rag{ Name: flagutil.PxeEnabledFlag, Value: &rags.Bool{Var: config.PxeEnabled}, Usage: "Enable pxe booting", Required: false, }, &rags.Rag{ Name: flagutil.AcRelayFlag, Value: &rags.Bool{Var: config.AcRelay}, Usage: "Enable activation code relaying for pxe-boot", Required: false, }, &rags.Rag{ Name: edgecliutils.BootstrapAckFlag, Value: &rags.Bool{Var: config.BootstrapAck}, Usage: "Bootstrap acknowledgement - require user confirmation for terminal bootstrap", Required: false, }, &rags.Rag{ Name: flagutil.VpnEnabledmentFlag, Value: &rags.Bool{Var: config.VpnEnabled}, Usage: "Enable vpn access on the cluster", Required: false, }, &rags.Rag{ Name: flagutil.ThickPosFlag, Value: &rags.Bool{Var: config.ThickPos, Default: true}, Usage: "Enable thick pos mode", Required: false, }, &rags.Rag{ Name: flagutil.EgressGatewayFlag, Value: &rags.Bool{Var: config.EgressGatewayEnabled}, Usage: "Enable egress gateway", Required: false, }, &rags.Rag{ Name: flagutil.GatewayRateLimitingFlag, Value: &rags.Bool{Var: config.GatewayRateLimitingEnabled}, Usage: "Enable gateway rate limiting", Required: false, }, &rags.Rag{ Name: flagutil.UplinkRateLimitFlag, Value: &rags.String{Var: config.UplinkRateLimit}, Usage: "A limit on the rate data can be uploaded", Required: false, }, &rags.Rag{ Name: flagutil.DownlinkRateLimitFlag, Value: &rags.String{Var: config.DownlinkRateLimit}, Usage: "A limit on the rate date can be downloaded", Required: false, }, &rags.Rag{ Name: flagutil.ClusterLogLevel, Value: &rags.String{Var: config.ClusterLogLevel}, Usage: "Cluster Level Log level for severity filtering", Required: false, }, &rags.Rag{ Name: flagutil.MaximumLanOutageHours, Value: &rags.Int{Var: config.MaximumLanOutageHours, Default: 96}, Usage: "Maximum LAN outage duration in hours", Required: false, }, ), Exec: func(_ context.Context, _ []string) error { //validates required field value, value cannot be empty or space if err := flagutil.ValidateRequiredFlags(cmd.Rags); err != nil { return err } registrar, err := constructors.BuildRegistrar(cmd.Rags) if err != nil { return err } reqCtx, cancelReq := context.WithTimeout(context.Background(), time.Duration(30)*time.Second) defer cancelReq() cluster, err := registrar.GetCluster(reqCtx, storeName, bannerName) if err != nil { return err } if flagutil.GetBoolFlag(cmd.Rags, flagutil.AcRelayFlag) { acRelay := flagutil.GetBoolFlag(cmd.Rags, flagutil.AcRelayFlag) config.AcRelay = &acRelay } if flagutil.GetBoolFlag(cmd.Rags, flagutil.PxeEnabledFlag) { pxeEnabled := flagutil.GetBoolFlag(cmd.Rags, flagutil.PxeEnabledFlag) config.PxeEnabled = &pxeEnabled } if flagutil.GetBoolFlag(cmd.Rags, flagutil.BootstrapAckFlag) { bootstrapAck := flagutil.GetBoolFlag(cmd.Rags, flagutil.BootstrapAckFlag) config.BootstrapAck = &bootstrapAck } if flagutil.GetBoolFlag(cmd.Rags, flagutil.VpnEnabledmentFlag) { vpnEnabled := flagutil.GetBoolFlag(cmd.Rags, flagutil.VpnEnabledmentFlag) config.VpnEnabled = &vpnEnabled } if flagutil.GetBoolFlag(cmd.Rags, flagutil.ThickPosFlag) { thickPos := flagutil.GetBoolFlag(cmd.Rags, flagutil.ThickPosFlag) config.ThickPos = &thickPos } if flagutil.GetBoolFlag(cmd.Rags, flagutil.EgressGatewayFlag) { egressGatewayEnabled := flagutil.GetBoolFlag(cmd.Rags, flagutil.EgressGatewayFlag) config.EgressGatewayEnabled = &egressGatewayEnabled } if flagutil.GetBoolFlag(cmd.Rags, flagutil.GatewayRateLimitingFlag) { gatewayRateLimitingEnabled := flagutil.GetBoolFlag(cmd.Rags, flagutil.GatewayRateLimitingFlag) config.GatewayRateLimitingEnabled = &gatewayRateLimitingEnabled } if flagutil.GetStringFlag(cmd.Rags, flagutil.UplinkRateLimitFlag) != "" { uplinkRateLimit := flagutil.GetStringFlag(cmd.Rags, flagutil.UplinkRateLimitFlag) config.UplinkRateLimit = &uplinkRateLimit if err := validateRateLimit(config.UplinkRateLimit); err != nil && config.UplinkRateLimit != nil { return fmt.Errorf("uplink validation failed: %w", err) } } if flagutil.GetStringFlag(cmd.Rags, flagutil.DownlinkRateLimitFlag) != "" { downlinkRateLimit := flagutil.GetStringFlag(cmd.Rags, flagutil.DownlinkRateLimitFlag) config.DownlinkRateLimit = &downlinkRateLimit if err := validateRateLimit(config.DownlinkRateLimit); err != nil && config.DownlinkRateLimit != nil { return fmt.Errorf("downlink validation failed: %w", err) } } if flagutil.GetStringFlag(cmd.Rags, flagutil.ClusterLogLevel) != "" { clusterLogLevel := flagutil.GetStringFlag(cmd.Rags, flagutil.ClusterLogLevel) config.ClusterLogLevel = &clusterLogLevel if err := validateLogLevel(config.ClusterLogLevel); err != nil { return fmt.Errorf("cluster Log Level Validation failed: %w", err) } } if flagutil.GetIntFlag(cmd.Rags, flagutil.MaximumLanOutageHours) >= 0 { maximumLanOutageHours := flagutil.GetIntFlag(cmd.Rags, flagutil.MaximumLanOutageHours) config.MaximumLanOutageHours = &maximumLanOutageHours if *config.MaximumLanOutageHours < 24 { return fmt.Errorf("maximum LAN outage duration must be at least 24 hours") } } reqCtx, cancelReq = context.WithTimeout(context.Background(), time.Duration(30)*time.Second) defer cancelReq() return registrar.UpdateClusterConfig(reqCtx, cluster.ClusterEdgeID, config) }, } return cmd } // Adding these validate function here, so if it fails, it doesn't have to wait til the end // and make another API call to validate func validateLogLevel(level *string) error { if utils.IsNullOrEmpty(level) { return nil } validLogLevels := map[string]bool{ "DEBUG": true, "INFO": true, "NOTICE": true, "WARNING": true, "ERROR": true, "CRITICAL": true, "ALERT": true, "EMERGENCY": true, } _, levelExists := validLogLevels[strings.ToUpper(*level)] var keys string for i := range validLogLevels { keys = keys + " " + i } if !levelExists { return fmt.Errorf("%s is not a valid log level. Please make sure it is one of the following levels: %s", *level, keys) } return nil } func validateRateLimit(rateLimit *string) error { if rateLimit == nil { return nil } rateLimitRegExp, err := regexp.Compile("^[1-9][0-9]*([kK]bit$|[mM]bit$)") if err != nil { return err } if !rateLimitRegExp.MatchString(*rateLimit) { return fmt.Errorf("rate limit does not match expected format in mbit or kbit. Example: 4mbit") } return nil }