feat: implement container network attaching
Containers can be attached: - after they are created, but before they are started - after a successful deploy, but before the proxy reloads This allows folks to have flexibility around when they would like a container to be made available to a network.
This commit is contained in:
5
plugins/network/.gitignore
vendored
5
plugins/network/.gitignore
vendored
@@ -1,10 +1,9 @@
|
||||
/commands
|
||||
/core-post-deploy
|
||||
/subcommands/*
|
||||
/triggers/*
|
||||
/triggers
|
||||
/network-*
|
||||
/install
|
||||
/post-app-clone-setup
|
||||
/post-create
|
||||
/post-delete
|
||||
/post-*
|
||||
/report
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
SUBCOMMANDS = subcommands/rebuild subcommands/rebuildall subcommands/report subcommands/set
|
||||
TRIGGERS = triggers/install triggers/network-build-config triggers/network-compute-ports triggers/network-config-exists triggers/network-get-ipaddr triggers/network-get-listeners triggers/network-get-port triggers/network-get-property triggers/network-write-ipaddr triggers/network-write-port triggers/post-app-clone-setup triggers/post-create triggers/post-delete triggers/report
|
||||
SUBCOMMANDS = subcommands/create subcommands/destroy subcommands/exists subcommands/info subcommands/list subcommands/rebuild subcommands/rebuildall subcommands/report subcommands/set
|
||||
TRIGGERS = triggers/core-post-deploy triggers/install triggers/network-build-config triggers/network-compute-ports triggers/network-config-exists triggers/network-get-ipaddr triggers/network-get-listeners triggers/network-get-port triggers/network-get-property triggers/network-write-ipaddr triggers/network-write-port triggers/post-app-clone-setup triggers/post-container-create triggers/post-create triggers/post-delete triggers/report
|
||||
BUILD = commands subcommands triggers
|
||||
PLUGIN_NAME = network
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -19,9 +21,55 @@ var (
|
||||
// DefaultProperties is a map of all valid network properties with corresponding default property values
|
||||
DefaultProperties = map[string]string{
|
||||
"bind-all-interfaces": "false",
|
||||
"attach-post-create": "",
|
||||
"attach-post-deploy": "",
|
||||
"tld": "",
|
||||
}
|
||||
)
|
||||
|
||||
// AttachAppToNetwork attaches a container to a network
|
||||
func AttachAppToNetwork(containerID string, networkName string, appName string, phase string, processType string) {
|
||||
cmdParts := []string{
|
||||
common.DockerBin(),
|
||||
"network",
|
||||
"connect",
|
||||
}
|
||||
|
||||
if phase == "deploy" {
|
||||
property := "tld"
|
||||
defaultValue := GetDefaultValue(property)
|
||||
tld := common.PropertyGetDefault("network", appName, property, defaultValue)
|
||||
|
||||
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)
|
||||
attachCmd := common.NewShellCmd(strings.Join(cmdParts, " "))
|
||||
var stderr bytes.Buffer
|
||||
attachCmd.ShowOutput = false
|
||||
attachCmd.Command.Stderr = &stderr
|
||||
_, err := attachCmd.Output()
|
||||
if err != nil {
|
||||
err = errors.New(strings.TrimSpace(stderr.String()))
|
||||
common.LogFail(fmt.Sprintf("Unable to attach container to network: %v", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
// BuildConfig builds network config files
|
||||
func BuildConfig(appName string) {
|
||||
if err := common.VerifyAppName(appName); err != nil {
|
||||
@@ -186,6 +234,21 @@ func HasNetworkConfig(appName string) bool {
|
||||
return common.FileExists(ipfile) && common.FileExists(portfile)
|
||||
}
|
||||
|
||||
// ListNetworks returns a list of docker networks
|
||||
func ListNetworks() ([]string, error) {
|
||||
b, err := sh.Command(common.DockerBin(), "network", "list", "--format", "{{ .Name }}").Output()
|
||||
output := strings.TrimSpace(string(b[:]))
|
||||
|
||||
networks := []string{}
|
||||
if err != nil {
|
||||
common.LogVerboseQuiet(output)
|
||||
return networks, err
|
||||
}
|
||||
|
||||
networks = strings.Split(output, "\n")
|
||||
return networks, nil
|
||||
}
|
||||
|
||||
// PostAppCloneSetup removes old IP and PORT files for a newly cloned app
|
||||
func PostAppCloneSetup(appName string) bool {
|
||||
dokkuRoot := common.MustGetEnv("DOKKU_ROOT")
|
||||
@@ -217,6 +280,8 @@ func ReportSingleApp(appName, infoFlag string) {
|
||||
|
||||
infoFlags := map[string]string{
|
||||
"--network-bind-all-interfaces": common.PropertyGet("network", appName, "bind-all-interfaces"),
|
||||
"--network-attach-post-create": common.PropertyGet("network", appName, "attach-post-create"),
|
||||
"--network-attach-post-deploy": common.PropertyGet("network", appName, "attach-post-deploy"),
|
||||
"--network-listeners": strings.Join(GetListeners(appName), " "),
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,38 @@ func main() {
|
||||
flag.Parse()
|
||||
|
||||
switch subcommand {
|
||||
case "create":
|
||||
networkName := flag.Arg(1)
|
||||
err := network.CommandCreate(networkName)
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
case "destroy":
|
||||
networkName := flag.Arg(1)
|
||||
forceDestroy := false
|
||||
if flag.Arg(2) == "force" {
|
||||
forceDestroy = true
|
||||
}
|
||||
err := network.CommandDestroy(networkName, forceDestroy)
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
case "exists":
|
||||
networkName := flag.Arg(1)
|
||||
err := network.CommandExists(networkName)
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
case "info":
|
||||
err := network.CommandInfo()
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
case "list":
|
||||
err := network.CommandList()
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
case "rebuild":
|
||||
appName := flag.Arg(1)
|
||||
network.BuildConfig(appName)
|
||||
|
||||
@@ -63,12 +63,22 @@ func main() {
|
||||
case "post-app-clone-setup":
|
||||
appName := flag.Arg(1)
|
||||
network.TriggerPostAppCloneSetup(appName)
|
||||
case "post-container-create":
|
||||
containerType := flag.Arg(0)
|
||||
containerID := flag.Arg(1)
|
||||
appName := flag.Arg(2)
|
||||
phase := flag.Arg(3)
|
||||
processType := flag.Arg(4)
|
||||
network.TriggerPostContainerCreate(containerType, containerID, appName, phase, processType)
|
||||
case "post-create":
|
||||
appName := flag.Arg(0)
|
||||
network.TriggerPostCreate(appName)
|
||||
case "post-delete":
|
||||
appName := flag.Arg(0)
|
||||
network.TriggerPostDelete(appName)
|
||||
case "core-post-deploy":
|
||||
appName := flag.Arg(0)
|
||||
network.TriggerCorePostDeploy(appName)
|
||||
case "report":
|
||||
appName := flag.Arg(0)
|
||||
network.ReportSingleApp(appName, "")
|
||||
|
||||
@@ -1,11 +1,132 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
// CommandCreate is an alias for "docker network create"
|
||||
func CommandCreate(networkName string) error {
|
||||
common.LogInfo1Quiet(fmt.Sprintf("Creating network %v", networkName))
|
||||
createCmd := common.NewShellCmd(strings.Join([]string{
|
||||
common.DockerBin(),
|
||||
"network",
|
||||
"create",
|
||||
"--attachable",
|
||||
"--label",
|
||||
"com.dokku.network=true",
|
||||
"--label",
|
||||
fmt.Sprintf("com.dokku.network-name=%v", networkName),
|
||||
networkName,
|
||||
}, " "))
|
||||
var stderr bytes.Buffer
|
||||
createCmd.ShowOutput = false
|
||||
createCmd.Command.Stderr = &stderr
|
||||
_, err := createCmd.Output()
|
||||
|
||||
if err != nil {
|
||||
err = errors.New(strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// CommandDestroy is an alias for "docker network rm"
|
||||
func CommandDestroy(networkName string, forceDestroy bool) error {
|
||||
if os.Getenv("DOKKU_APPS_FORCE_DELETE") == "1" {
|
||||
forceDestroy = true
|
||||
}
|
||||
|
||||
if !forceDestroy {
|
||||
common.LogWarn("WARNING: Potentially Destructive Action")
|
||||
common.LogWarn(fmt.Sprintf("This command will destroy network %v.", networkName))
|
||||
common.LogWarn(fmt.Sprintf("To proceed, type \"%v\"", networkName))
|
||||
fmt.Print("> ")
|
||||
var response string
|
||||
_, err := fmt.Scanln(&response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if response != networkName {
|
||||
common.LogStderr("Confirmation did not match test. Aborted.")
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
common.LogInfo1Quiet(fmt.Sprintf("Destroying network %v", networkName))
|
||||
destroyCmd := common.NewShellCmd(strings.Join([]string{
|
||||
common.DockerBin(),
|
||||
"network",
|
||||
"rm",
|
||||
networkName,
|
||||
}, " "))
|
||||
var stderr bytes.Buffer
|
||||
destroyCmd.ShowOutput = false
|
||||
destroyCmd.Command.Stderr = &stderr
|
||||
_, err := destroyCmd.Output()
|
||||
|
||||
if err != nil {
|
||||
err = errors.New(strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// CommandExists checks if a network exists
|
||||
func CommandExists(networkName string) error {
|
||||
exists := false
|
||||
if networkName == "" {
|
||||
return errors.New("No network name specified")
|
||||
}
|
||||
|
||||
networks, err := ListNetworks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, n := range networks {
|
||||
if networkName == n {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if exists {
|
||||
common.LogInfo1Quiet("Network exists")
|
||||
} else {
|
||||
common.LogFail("Network does not exist")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandInfo is an alias for "docker network inspect"
|
||||
func CommandInfo() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandList is an alias for "docker network ls"
|
||||
func CommandList() error {
|
||||
networks, err := ListNetworks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
common.LogInfo2Quiet("Networks")
|
||||
for _, networkName := range networks {
|
||||
fmt.Println(networkName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandRebuildall rebuilds network settings for all apps
|
||||
func CommandRebuildall() {
|
||||
apps, err := common.DokkuApps()
|
||||
@@ -44,5 +165,18 @@ func CommandSet(appName string, property string, value string) {
|
||||
value = "false"
|
||||
}
|
||||
|
||||
attachProperites := map[string]bool{
|
||||
"attach-post-create": true,
|
||||
"attach-post-deploy": true,
|
||||
}
|
||||
|
||||
invalidNetworks := map[string]bool{
|
||||
"host": true,
|
||||
"bridge": true,
|
||||
}
|
||||
if attachProperites[property] && invalidNetworks[value] {
|
||||
common.LogFail("Invalid network name specified for attach")
|
||||
}
|
||||
|
||||
common.CommandPropertySet("network", appName, property, value, DefaultProperties)
|
||||
}
|
||||
|
||||
@@ -161,6 +161,22 @@ func TriggerPostAppCloneSetup(appName string) {
|
||||
}
|
||||
}
|
||||
|
||||
// TriggerPostContainerCreate associates the container with a specified network
|
||||
func TriggerPostContainerCreate(containerType string, containerID string, appName string, phase string, processType string) {
|
||||
if containerType != "app" {
|
||||
return
|
||||
}
|
||||
|
||||
property := "attach-post-create"
|
||||
defaultValue := GetDefaultValue(property)
|
||||
networkName := common.PropertyGetDefault("network", appName, property, defaultValue)
|
||||
if networkName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
AttachAppToNetwork(containerID, networkName, appName, phase, processType)
|
||||
}
|
||||
|
||||
// TriggerPostCreate sets bind-all-interfaces to false by default
|
||||
func TriggerPostCreate(appName string) {
|
||||
err := common.PropertyWrite("network", appName, "bind-all-interfaces", "false")
|
||||
@@ -176,3 +192,27 @@ func TriggerPostDelete(appName string) {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// TriggerCorePostDeploy associates the container with a specified network
|
||||
func TriggerCorePostDeploy(appName string) {
|
||||
property := "attach-post-deploy"
|
||||
defaultValue := GetDefaultValue(property)
|
||||
networkName := common.PropertyGetDefault("network", appName, property, defaultValue)
|
||||
if networkName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
common.LogInfo1Quiet(fmt.Sprintf("Associating app with network %s", networkName))
|
||||
containerIDs, err := common.GetAppRunningContainerIDs(appName, "")
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
|
||||
for _, containerID := range containerIDs {
|
||||
processType, err := common.DockerInspect(containerID, "{{ index .Config.Labels \"com.dokku.process-type\"}}")
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
AttachAppToNetwork(containerID, networkName, appName, "deploy", processType)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user