Files
dokku/plugins/network/functions.go
Jose Diaz-Gonzalez 61b7752cce fix: implement missing network:info command
Also add json format output to the network:list command.

Closes #7093
2024-09-23 20:27:13 -04:00

214 lines
5.2 KiB
Go

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) {
return nil
}
cmdParts := []string{
"network",
"connect",
}
if phase == "deploy" {
tld := reportComputedTld(appName)
networkAlias := fmt.Sprintf("%v.%v", appName, processType)
if tld != "" {
networkAlias = fmt.Sprintf("%v.%v", networkAlias, tld)
}
cmdParts = append(cmdParts, "--alias")
cmdParts = append(cmdParts, networkAlias)
hostname, err := common.DockerInspect(containerID, "{{ .Config.Hostname }}")
if err != nil {
common.LogWarn(err.Error())
} else {
cmdParts = append(cmdParts, "--alias")
cmdParts = append(cmdParts, fmt.Sprintf("%v.%v", hostname, networkAlias))
}
}
cmdParts = append(cmdParts, networkName)
cmdParts = append(cmdParts, containerID)
result, err := common.CallExecCommand(common.ExecCommandInput{
Command: common.DockerBin(),
Args: cmdParts,
})
if err != nil {
return fmt.Errorf("Unable to attach container to network: %w", err)
}
if result.ExitCode != 0 {
return fmt.Errorf("Unable to attach container to network: %s", result.StderrContents())
}
return nil
}
// isContainerInNetwork returns true if the container is already attached to the specified network
func isContainerInNetwork(containerID string, networkName string) bool {
result, err := common.CallExecCommand(common.ExecCommandInput{
Command: common.DockerBin(),
Args: []string{"container", "inspect", "--format", "{{range $net, $v := .NetworkSettings.Networks}}{{println $net}}{{end}}", containerID},
})
if err != nil {
common.LogVerboseQuiet(fmt.Sprintf("Error checking container networking status: %v", err.Error()))
return false
}
if result.ExitCode != 0 {
common.LogVerboseQuiet(fmt.Sprintf("Error checking container networking status: %v", result.StderrContents()))
return false
}
for _, line := range strings.Split(result.StdoutContents(), "\n") {
network := strings.TrimSpace(line)
if network == "" {
continue
}
if network == networkName {
return true
}
}
return false
}
// isConflictingPropertyValue returns true if the other attach property has a conflicting value
func isConflictingPropertyValue(appName string, property string, value string) bool {
if value == "" {
return false
}
otherValue := ""
if property == "attach-post-create" {
otherValue = reportComputedAttachPostDeploy(appName)
} else {
otherValue = reportComputedAttachPostCreate(appName)
}
return value == otherValue
}
// networkExists checks to see if a network exists
func networkExists(networkName string) (bool, error) {
if networkName == "" {
return false, errors.New("No network name specified")
}
exists := false
networks, err := getNetworks()
if err != nil {
return false, err
}
for _, network := range networks {
if networkName == network.Name {
exists = true
break
}
}
return exists, nil
}
// 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", "json"},
})
if err != nil {
common.LogVerboseQuiet(result.StderrContents())
return map[string]DockerNetwork{}, err
}
if result.ExitCode != 0 {
common.LogVerboseQuiet(result.StderrContents())
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
}
return networks, nil
}