package modify import ( "context" "errors" "fmt" "time" "edge-infra.dev/pkg/edge/api/graph/model" "edge-infra.dev/pkg/edge/constants" "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" "edge-infra.dev/pkg/lib/networkvalidator" ) var ( networkAddressValidationMap = map[string]struct { validateFunc func(string) bool inetType string errorFormat string }{ flagutil.IPv4Flag: { validateFunc: func(ip string) bool { return networkvalidator.IsValidDomain(ip) || networkvalidator.ValidateIP(ip) }, inetType: "inet", errorFormat: "invalid IPv4 address or domain %s", }, flagutil.IPv6Flag: { validateFunc: func(ip string) bool { return networkvalidator.IsValidDomain(ip) || networkvalidator.ValidateIP(ip) }, inetType: "inet6", errorFormat: "invalid IPv6 address or domain %s", }, flagutil.CIDRFlag: { validateFunc: networkvalidator.IsValidCIDR, inetType: "inet", errorFormat: "invalid CIDR range %s", }, } ) func NewCmd(cfg *edgecli.Config) *command.Command { var cmd *command.Command flagsets := flagutil.GetConnectionFlags(cfg) var storeName, bannerName, networkServiceID string cmd = &command.Command{ ShortUsage: "edge networkservice modify", ShortHelp: "Modify a Network Service belonging to a Cluster", 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, }, // At least one of these must be passed. Check later &rags.Rag{ Name: flagutil.IPv4Flag, Usage: "IPv4 address of service", Value: &rags.String{}, Required: false, }, &rags.Rag{ Name: flagutil.IPv6Flag, Usage: "IPv6 address of service", Value: &rags.String{}, Required: false, }, &rags.Rag{ Name: flagutil.CIDRFlag, Usage: "CIDR range of service", Value: &rags.String{}, Required: false, }, &rags.Rag{ Name: flagutil.NetworkServiceIDFlag, Value: &rags.String{Var: &networkServiceID}, Usage: "ID of network service", Required: true, }, &rags.Rag{ Name: flagutil.Priority, Usage: "Priority of the network service (must be a positive int)", Required: false, Value: &rags.Int{Default: 100}, }, ), Exec: func(_ context.Context, _ []string) error { if err := flagutil.ValidateRequiredFlags(cmd.Rags); err != nil { return err } registrar, err := constructors.BuildRegistrar(cmd.Rags) if err != nil { return err } // For modification, you must only provide one or the other, as the DB has no // way of representing a single network service as having both an IPv4 and IPv6 // address (they have to be represented as two separate services). var passedFlags []string // Append IPv4 flag if provided if flagutil.GetStringFlag(cmd.Rags, flagutil.IPv4Flag) != "" { passedFlags = append(passedFlags, flagutil.IPv4Flag) } // Append IPv6 flag if provided if flagutil.GetStringFlag(cmd.Rags, flagutil.IPv6Flag) != "" { passedFlags = append(passedFlags, flagutil.IPv6Flag) } // Append CIDR flag if provided if flagutil.GetStringFlag(cmd.Rags, flagutil.CIDRFlag) != "" { passedFlags = append(passedFlags, flagutil.CIDRFlag) } if len(passedFlags) != 1 { return errors.New("must pass exactly one of --ipv4, --ipv6, --cidr") } flag := passedFlags[0] networkServiceID := flagutil.GetStringFlag(cmd.Rags, flagutil.NetworkServiceIDFlag) storeName := flagutil.GetStringFlag(cmd.Rags, flagutil.StoreFlag) bannerName := flagutil.GetStringFlag(cmd.Rags, flagutil.BannerFlag) reqCtx, cancelReq := context.WithTimeout(context.Background(), time.Duration(30)*time.Second) defer cancelReq() cluster, err := registrar.GetCluster(reqCtx, storeName, bannerName) if err != nil { fmt.Println("an error occurred whilst modifying the cluster network service") return err } var serviceType string for _, netService := range cluster.ClusterNetworkServices { if netService.NetworkServiceID == networkServiceID { serviceType = netService.ServiceType break } } if serviceType == "" { return errors.New("the networkServiceID provided does not exist in this cluster") } if serviceType == constants.ServiceTypeClusterDNS { return fmt.Errorf("%s is not directly configurable - update %s instead", constants.ServiceTypeClusterDNS, constants.ServiceTypeServiceNetworkCIDR) } ipAddress, ipFamily, err := validateNetworkAddress(cmd.Rags, flag, serviceType) if err != nil { return err } netService := model.UpdateNetworkServiceInfo{} if flagutil.GetBoolFlag(cmd.Rags, flagutil.Priority) { priority := flagutil.GetIntFlag(cmd.Rags, flagutil.Priority) netService.Priority = &priority } netService.NetworkServiceID = networkServiceID netService.IP = ipAddress netService.Family = ipFamily // The elements of the slice cannot be pointers, otherwise they will map to a // _nullable_ GraphQL type, which isn't what we want. networkServices := []model.UpdateNetworkServiceInfo{ netService, } fmt.Println("modifying network services...") reqCtx, cancelReq = context.WithTimeout(context.Background(), time.Duration(30)*time.Second) defer cancelReq() if _, err = registrar.ModifyNetworkServices(reqCtx, cluster.ClusterEdgeID, networkServices); err != nil { fmt.Println("an error occurred whilst modifying network services") return err } fmt.Println("network services modified successfully!") return nil }, } return cmd } func validateNetworkAddress(rs *rags.RagSet, flag string, serviceType string) (string, string, error) { switch serviceType { case constants.ServiceTypePodNetworkCIDR, constants.ServiceTypeServiceNetworkCIDR: if flag == flagutil.IPv4Flag || flag == flagutil.IPv6Flag { fmt.Println(flag) return "", "", fmt.Errorf("k8s network services must be CIDR ranges - use --cidr not --ipv4/--ipv6") } default: if flag == flagutil.CIDRFlag { fmt.Println(flag) return "", "", fmt.Errorf("only k8s network services should be CIDR ranges - use --ipv4 or --ipv6, not --cidr") } } ipAddress := flagutil.GetStringFlag(rs, flag) if validation, ok := networkAddressValidationMap[flag]; ok { if !validation.validateFunc(ipAddress) { return "", "", fmt.Errorf(validation.errorFormat, ipAddress) } return ipAddress, validation.inetType, nil } return "", "", fmt.Errorf("couldn't validate unknown flag %s", flag) }