package client import ( "bufio" "fmt" "log" "os" "strings" "text/tabwriter" "golang.org/x/net/context" "github.com/hyperhq/hyper-api/types" "github.com/hyperhq/hyper-api/types/filters" Cli "github.com/hyperhq/hypercli/cli" "github.com/hyperhq/hypercli/opts" flag "github.com/hyperhq/hypercli/pkg/mflag" ) var warnMessage = "Please note that Floating IP (FIP) is billed monthly. The billing begins when a new IP is allocated, ends when it is released. Partial month is treated as a entire month. Do you want to continue?" // CmdFip is the parent subcommand for all fip commands // // Usage: docker fip [OPTIONS] func (cli *DockerCli) CmdFip(args ...string) error { cmd := Cli.Subcmd("fip", []string{"COMMAND [OPTIONS]"}, fipUsage(), false) cmd.Require(flag.Min, 1) err := cmd.ParseFlags(args, true) cmd.Usage() return err } // CmdNetworkCreate creates a new fip with a given name // // Usage: docker fip create [OPTIONS] COUNT func (cli *DockerCli) CmdFipAllocate(args ...string) error { cmd := Cli.Subcmd("fip allocate", []string{"COUNT"}, "Creates some new floating IPs by the user", false) flAvailable := cmd.Bool([]string{"-pick"}, false, "Pick an available floating IP if have") flForce := cmd.Bool([]string{"y", "-yes"}, false, "Agree to allocate floating IP, will not show prompt") cmd.Require(flag.Exact, 1) err := cmd.ParseFlags(args, true) if err != nil { return err } if *flAvailable == true { fipFilterArgs, _ := filters.FromParam("dangling=true") options := types.NetworkListOptions{ Filters: fipFilterArgs, } fips, err := cli.client.FipList(context.Background(), options) if err == nil { for _, fip := range fips { if fip["container"] == "" && fip["service"] == "" { fmt.Fprintf(cli.out, "%s\n", fip["fip"]) return nil } } } } if *flForce == false { if askForConfirmation(warnMessage) == false { return nil } } fips, err := cli.client.FipAllocate(context.Background(), cmd.Arg(0)) if err != nil { return err } for _, ip := range fips { fmt.Fprintf(cli.out, "%s\n", ip) } return nil } // CmdFipRelease deletes one or more fips // // Usage: docker fip release FIP [FIP...] func (cli *DockerCli) CmdFipRelease(args ...string) error { cmd := Cli.Subcmd("fip release", []string{"FIP [FIP...]"}, "Release one or more fips", false) cmd.Require(flag.Min, 1) if err := cmd.ParseFlags(args, true); err != nil { return err } status := 0 for _, ip := range cmd.Args() { if err := cli.client.FipRelease(context.Background(), ip); err != nil { fmt.Fprintf(cli.err, "%s\n", err) status = 1 continue } } if status != 0 { return Cli.StatusError{StatusCode: status} } return nil } // CmdFipAttach connects a container to a floating IP // // Usage: docker fip attach [OPTIONS] func (cli *DockerCli) CmdFipAttach(args ...string) error { cmd := Cli.Subcmd("fip attach", []string{"FIP CONTAINER"}, "Connects a container to a floating IP", false) flForce := cmd.Bool([]string{"f", "-force"}, false, "Deattach that FIP and attach it to this container") cmd.Require(flag.Min, 2) if err := cmd.ParseFlags(args, true); err != nil { return err } if *flForce { filter, _ := filters.FromParam("dangling=false") options := types.NetworkListOptions{ Filters: filter, } fips, err := cli.client.FipList(context.Background(), options) if err != nil { return err } for _, fip := range fips { if ip := fip["fip"]; ip == cmd.Arg(0) { if fip["container"] != "" { cli.client.FipDetach(context.Background(), fip["container"]) } else if fip["service"] != "" { ip = "" sv := types.ServiceUpdate{ FIP: &ip, } cli.client.ServiceUpdate(context.Background(), fip["service"], sv) } break } } } return cli.client.FipAttach(context.Background(), cmd.Arg(0), cmd.Arg(1)) } // CmdFipDetach disconnects a container from a floating IP // // Usage: docker fip detach func (cli *DockerCli) CmdFipDetach(args ...string) error { cmd := Cli.Subcmd("fip detach", []string{"CONTAINER"}, "Disconnects container from a floating IP", false) //force := cmd.Bool([]string{"f", "-force"}, false, "Force the container to disconnect from a floating IP") cmd.Require(flag.Exact, 1) if err := cmd.ParseFlags(args, true); err != nil { return err } ip, err := cli.client.FipDetach(context.Background(), cmd.Arg(0)) if err != nil { return err } fmt.Fprintf(cli.out, "%s\n", ip) return nil } // CmdFipLs lists all the fips // // Usage: docker fip ls [OPTIONS] func (cli *DockerCli) CmdFipLs(args ...string) error { cmd := Cli.Subcmd("fip ls", nil, "Lists fips", true) flFilter := opts.NewListOpts(nil) cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided") cmd.Require(flag.Exact, 0) err := cmd.ParseFlags(args, true) if err != nil { return err } // Consolidate all filter flags, and sanity check them early. // They'll get process after get response from server. fipFilterArgs := filters.NewArgs() for _, f := range flFilter.GetAll() { if fipFilterArgs, err = filters.ParseFlag(f, fipFilterArgs); err != nil { return err } } options := types.NetworkListOptions{ Filters: fipFilterArgs, } fips, err := cli.client.FipList(context.Background(), options) if err != nil { return err } w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) fmt.Fprintf(w, "Floating IP\tName\tContainer\tService\n") for _, fip := range fips { fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", fip["fip"], fip["name"], fip["container"], fip["service"]) } w.Flush() return nil } func (cli *DockerCli) CmdFipName(args ...string) error { cmd := Cli.Subcmd("fip name", []string{"FIP [NAME]"}, "Set a name for a floating IP", false) //force := cmd.Bool([]string{"f", "-force"}, false, "Force the container to disconnect from a floating IP") cmd.Require(flag.Min, 1) cmd.Require(flag.Max, 2) if err := cmd.ParseFlags(args, true); err != nil { return err } err := cli.client.FipName(context.Background(), cmd.Arg(0), cmd.Arg(1)) if err != nil { return err } return nil } func fipUsage() string { fipCommands := [][]string{ {"allocate", "Allocate a or some IPs"}, {"attach", "Attach floating IP to container"}, {"detach", "Detach floating IP from container"}, {"ls", "List all floating IPs"}, {"release", "Release a floating IP"}, {"name", "Name a floating IP"}, } help := "Commands:\n" for _, cmd := range fipCommands { help += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1]) } help += fmt.Sprintf("\nRun 'hyper fip COMMAND --help' for more information on a command.") return help } // Allocate and attach a fip func (cli *DockerCli) associateNewFip(ctx context.Context, contID string) (string, error) { fips, err := cli.client.FipAllocate(ctx, "1") if err != nil { return "", err } for _, ip := range fips { err = cli.client.FipAttach(ctx, ip, contID) if err != nil { go func() { cli.client.FipRelease(ctx, ip) }() return "", err } return ip, nil } return "", fmt.Errorf("Server failed to create new fip") } // Release a fip func (cli *DockerCli) releaseFip(ctx context.Context, ip string) error { return cli.client.FipRelease(ctx, ip) } // Detach and release a fip func (cli *DockerCli) releaseContainerFip(ctx context.Context, contID string) error { ip, err := cli.client.FipDetach(ctx, contID) if err != nil { return err } return cli.client.FipRelease(ctx, ip) } // askForConfirmation asks the user for confirmation. A user must type in "yes" or "no" and // then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as // confirmations. If the input is not recognized, it will ask again. The function does not return // until it gets a valid response from the user. func askForConfirmation(s string) bool { reader := bufio.NewReader(os.Stdin) for { fmt.Printf("%s [y/n]: ", s) response, err := reader.ReadString('\n') if err != nil { log.Fatal(err) } response = strings.ToLower(strings.TrimSpace(response)) if response == "y" || response == "yes" { return true } else if response == "n" || response == "no" { return false } } }