diff --git a/docs/networking/network.md b/docs/networking/network.md index f1c24bfba..aa9fc1311 100644 --- a/docs/networking/network.md +++ b/docs/networking/network.md @@ -4,15 +4,15 @@ > New as of 0.11.0, Enhanced in 0.20.0 ``` -network:create # Creates an attachable docker network -network:destroy # Destroys a docker network -network:exists # Checks if a docker network exists -network:info # Outputs information about a docker network -network:list # Lists all docker networks -network:report [] [] # Displays a network report for one or more apps -network:rebuild # Rebuilds network settings for an app -network:rebuildall # Rebuild network settings for all apps -network:set () # Set or clear a network property for an app +network:create # Creates an attachable docker network +network:destroy # Destroys a docker network +network:exists # Checks if a docker network exists +network:info [--format text|json] # Outputs information about a docker network +network:list [--format text|json] # Lists all docker networks +network:report [] [] # Displays a network report for one or more apps +network:rebuild # Rebuilds network settings for an app +network:rebuildall # Rebuild network settings for all apps +network:set () # Set or clear a network property for an app ``` The Network plugin allows developers to abstract the concept of container network management, allowing developers to both change what networks a given container is attached to as well as rebuild the configuration on the fly. @@ -51,6 +51,21 @@ none test-network ``` +The `network:list` command also takes a `--format` flag, with the valid options including `text` (default) and `json`. The `json` output format can be used for automation purposes: + +```shell +dokku network:list --format json +``` + +``` +[ + {"CreatedAt":"2024-02-25T01:55:24.275184461Z","Driver":"bridge","ID":"d18df2d21433","Internal":false,"IPv6":false,"Labels":{},"Name":"bridge","Scope":"local"}, + {"CreatedAt":"2024-02-25T01:55:24.275184461Z","Driver":"bridge","ID":"f50fa882e7de","Internal":false,"IPv6":false,"Labels":{},"Name":"test-network","Scope":"local"}, + {"CreatedAt":"2024-02-25T01:55:24.275184461Z","Driver":"host","ID":"ab6a59291443","Internal":false,"IPv6":false,"Labels":{},"Name":"host","Scope":"local"}, + {"CreatedAt":"2024-02-25T01:55:24.275184461Z","Driver":"null","ID":"e2506bc8b7d7","Internal":false,"IPv6":false,"Labels":{},"Name":"none","Scope":"local"} +] +``` + ### Creating a network > [!IMPORTANT] @@ -117,16 +132,30 @@ The `network:exists` command will return non-zero if the network does not exist, ### Checking network info > [!IMPORTANT] -> New as of 0.20.0, Requires Docker 1.21+ +> New as of 0.35.3 Network information can be retrieved via the `network:info` command. This is a slightly different version of the `docker network` command. ```shell -dokku network:info test-network +dokku network:info bridge ``` ``` -// TODO +=====> bridge network information + ID: d18df2d21433 + Name: bridge + Driver: bridge + Scope: local +``` + +The `network:info` command also takes a `--format` flag, with the valid options including `text` (default) and `json`. The `json` output format can be used for automation purposes: + +```shell +dokku network:info bridge --format json +``` + +``` +{"CreatedAt":"2024-02-25T01:55:24.275184461Z","Driver":"bridge","ID":"d18df2d21433","Internal":false,"IPv6":false,"Labels":{},"Name":"bridge","Scope":"local"} ``` ### Routing an app to a known ip:port combination diff --git a/plugins/network/functions.go b/plugins/network/functions.go index eb9e096c4..f60ac266e 100644 --- a/plugins/network/functions.go +++ b/plugins/network/functions.go @@ -1,13 +1,27 @@ package network import ( + "encoding/json" "errors" "fmt" + "strconv" "strings" + "time" "github.com/dokku/dokku/plugins/common" ) +type DockerNetwork struct { + CreatedAt time.Time + Driver string + ID string + Internal bool + IPv6 bool + Labels map[string]string + Name string + Scope string +} + // attachAppToNetwork attaches a container to a network func attachAppToNetwork(containerID string, networkName string, appName string, phase string, processType string) error { if isContainerInNetwork(containerID, networkName) { @@ -110,13 +124,13 @@ func networkExists(networkName string) (bool, error) { exists := false - networks, err := listNetworks() + networks, err := getNetworks() if err != nil { return false, err } - for _, n := range networks { - if networkName == n { + for _, network := range networks { + if networkName == network.Name { exists = true break } @@ -125,21 +139,75 @@ func networkExists(networkName string) (bool, error) { return exists, nil } -// listNetworks returns a list of docker networks -func listNetworks() ([]string, error) { +// getNetworks returns a list of docker networks +func getNetworks() (map[string]DockerNetwork, error) { result, err := common.CallExecCommand(common.ExecCommandInput{ Command: common.DockerBin(), - Args: []string{"network", "ls", "--format", "{{ .Name }}"}, + Args: []string{"network", "ls", "--format", "json"}, }) if err != nil { common.LogVerboseQuiet(result.StderrContents()) - return []string{}, err + return map[string]DockerNetwork{}, err } if result.ExitCode != 0 { common.LogVerboseQuiet(result.StderrContents()) - return []string{}, fmt.Errorf("Unable to list networks") + return map[string]DockerNetwork{}, fmt.Errorf("Unable to list networks") + } + + networkLines := strings.Split(result.StdoutContents(), "\n") + networks := map[string]DockerNetwork{} + for _, line := range networkLines { + if line == "" { + continue + } + result := make(map[string]interface{}) + err := json.Unmarshal([]byte(line), &result) + if err != nil { + return map[string]DockerNetwork{}, err + } + + network := DockerNetwork{ + Driver: result["Driver"].(string), + ID: result["ID"].(string), + Name: result["Name"].(string), + Scope: result["Scope"].(string), + Labels: map[string]string{}, + } + + if createdAtVal := result["CreatedAt"].(string); createdAtVal != "" { + createdAt, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", "2024-02-25 01:55:24.275184461 +0000 UTC") + if err == nil { + network.CreatedAt = createdAt + } + } + + if ipv6Val := result["IPv6"].(string); ipv6Val != "" { + val, err := strconv.ParseBool(ipv6Val) + if err == nil { + network.IPv6 = val + } + } + if internalVal := result["Internal"].(string); internalVal != "" { + val, err := strconv.ParseBool(internalVal) + if err == nil { + network.Internal = val + } + } + + labels := strings.Split(result["Labels"].(string), ",") + for _, v := range labels { + parts := strings.SplitN(v, "=", 2) + if len(parts) != 2 { + continue + } + + key := parts[0] + value := parts[1] + network.Labels[key] = value + } + + networks[network.Name] = network } - networks := strings.Split(result.StdoutContents(), "\n") return networks, nil } diff --git a/plugins/network/src/commands/commands.go b/plugins/network/src/commands/commands.go index 80898c137..f79bda5d0 100644 --- a/plugins/network/src/commands/commands.go +++ b/plugins/network/src/commands/commands.go @@ -21,8 +21,8 @@ Additional commands:` network:create , Creates an attachable docker network network:destroy , Destroys a docker network network:exists , Checks if a docker network exists - network:info , Outputs information about a docker network - network:list, Lists all docker networks + network:info [--format text|json], Outputs information about a docker network + network:list [--format text|json], Lists all docker networks network:rebuild , Rebuilds network settings for an app network:rebuildall, Rebuild network settings for all apps network:report [] [], Displays a network report for one or more apps diff --git a/plugins/network/src/subcommands/subcommands.go b/plugins/network/src/subcommands/subcommands.go index e81c49be1..359a3d37a 100644 --- a/plugins/network/src/subcommands/subcommands.go +++ b/plugins/network/src/subcommands/subcommands.go @@ -36,12 +36,15 @@ func main() { err = network.CommandExists(networkName) case "info": args := flag.NewFlagSet("network:info", flag.ExitOnError) + format := args.String("format", "text", "format: [ text | json ]") args.Parse(os.Args[2:]) - err = network.CommandInfo() + networkName := args.Arg(0) + err = network.CommandInfo(networkName, *format) case "list": args := flag.NewFlagSet("network:list", flag.ExitOnError) + format := args.String("format", "text", "format: [ text | json ]") args.Parse(os.Args[2:]) - err = network.CommandList() + err = network.CommandList(*format) case "rebuild": args := flag.NewFlagSet("network:rebuild", flag.ExitOnError) args.Parse(os.Args[2:]) diff --git a/plugins/network/subcommands.go b/plugins/network/subcommands.go index cc2865dc0..2dc907055 100644 --- a/plugins/network/subcommands.go +++ b/plugins/network/subcommands.go @@ -1,6 +1,7 @@ package network import ( + "encoding/json" "errors" "fmt" "os" @@ -74,20 +75,67 @@ func CommandExists(networkName string) error { } // CommandInfo is an alias for "docker network inspect" -func CommandInfo() error { - return nil -} +func CommandInfo(networkName string, format string) error { + if networkName == "" { + return errors.New("No network name specified") + } -// CommandList is an alias for "docker network ls" -func CommandList() error { - networks, err := listNetworks() + if format != "json" && format != "text" { + return errors.New("Invalid format specified, use either text or json") + } + + networks, err := getNetworks() if err != nil { return err } + network, ok := networks[networkName] + if !ok { + return errors.New("Network does not exist") + } + + if format == "json" { + out, err := json.Marshal(network) + if err != nil { + return err + } + common.Log(string(out)) + return nil + } + + length := 10 + common.LogInfo2Quiet(fmt.Sprintf("%s network information", networkName)) + common.LogVerbose(fmt.Sprintf("%s%s", common.RightPad("ID:", length, " "), network.ID)) + common.LogVerbose(fmt.Sprintf("%s%s", common.RightPad("Name:", length, " "), network.Name)) + common.LogVerbose(fmt.Sprintf("%s%s", common.RightPad("Driver:", length, " "), network.Driver)) + common.LogVerbose(fmt.Sprintf("%s%s", common.RightPad("Scope:", length, " "), network.Scope)) + + return nil +} + +// CommandList is an alias for "docker network ls" +func CommandList(format string) error { + networks, err := getNetworks() + if err != nil { + return err + } + + if format == "json" { + networkList := []DockerNetwork{} + for _, network := range networks { + networkList = append(networkList, network) + } + out, err := json.Marshal(networkList) + if err != nil { + return err + } + common.Log(string(out)) + return nil + } + common.LogInfo2Quiet("Networks") - for _, networkName := range networks { - fmt.Println(networkName) + for _, network := range networks { + fmt.Println(network.Name) } return nil