diff --git a/.editorconfig b/.editorconfig index 2c518168b..b4d8d412f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,7 +15,7 @@ insert_final_newline = true indent_style = tab indent_size = 4 -[*.go] +[*.{go,mod}] insert_final_newline = true indent_style = tab indent_size = 4 diff --git a/docs/appendices/0.25.0-migration-guide.md b/docs/appendices/0.25.0-migration-guide.md new file mode 100644 index 000000000..6361d512d --- /dev/null +++ b/docs/appendices/0.25.0-migration-guide.md @@ -0,0 +1,5 @@ +# 0.25.0 Migration Guide + +## Changes + +- The network plugin can now set an `initial-network` for all containers on creation. This is a replacement for specifying the `--network` flag via the `docker-options` plugin. Please see the [network documentation](/docs/networking/network.md#attaching-an-app-to-a-network) for more information. diff --git a/docs/networking/network.md b/docs/networking/network.md index 7e87636b2..01869cece 100644 --- a/docs/networking/network.md +++ b/docs/networking/network.md @@ -127,7 +127,7 @@ dokku network:info test-network > New as of 0.20.0, Requires Docker 1.21+ -Apps will default to being associated with the `bridge` network, but can be attached to `attachable` networks by changing the `attach-post-create` or `attach-post-deploy` network properties when using the [docker-local scheduler](/docs/advanced-usage/schedulers/docker-local.md). A change in these values will require an app deploy or rebuild. +Apps will default to being associated with the default `bridge` network or a network specified by the `initial-network` network property. Additionally, an app can be attached to `attachable` networks by changing the `attach-post-create` or `attach-post-deploy` network properties when using the [docker-local scheduler](/docs/advanced-usage/schedulers/docker-local.md). A change in these values will require an app deploy or rebuild. ```shell # associates the network after a container is created but before it is started @@ -135,6 +135,9 @@ dokku network:set node-js-app attach-post-create test-network # associates the network after the deploy is successful but before the proxy is updated dokku network:set node-js-app attach-post-deploy other-test-network + +# associates the network at container creation +dokku network:set node-js-app initial-network global-network ``` Setting the `attach` network property to an empty value will de-associate the container with the network. @@ -142,6 +145,23 @@ Setting the `attach` network property to an empty value will de-associate the co ```shell dokku network:set node-js-app attach-post-create dokku network:set node-js-app attach-post-deploy +dokku network:set node-js-app initial-network +``` + +The network properties can also be set globally. The global default is emty string, and the global value is used when no app-specific value is set. + +```shell +dokku network:set --global attach-post-create global-create-network +dokku network:set --global attach-post-deploy global-deploy-network +dokku network:set --global initial-network global-network +``` + +The default value may be set by passing an empty value for the option. + +```shell +dokku network:set --global attach-post-create +dokku network:set --global attach-post-deploy +dokku network:set --global initial-network ``` #### Network Aliases @@ -162,7 +182,7 @@ Dockerfile-based applications may listen on other ports. For more information on #### Specifying a custom TLD -When attaching applications to networks, a custom TLD can be specified via the `network:set` command. This TLD is suffixed to the network alias for the application/process-type combination for _all_ networks to which the application is attached, and cannot be customized per network. +When attaching applications to networks, a custom TLD can be specified via the `network:set` command. This TLD is suffixed to the network alias for the application/process-type combination for _all_ networks to which the application is attached, and cannot be customized per network. The default value is an empty string. To specify a TLD of `svc.cluster.local` for your application, run the following command: @@ -179,6 +199,24 @@ node-js-app.web.svc.cluster.local Note that this has no impact on container port handling, and users must still specify the container port when making internal network requests. +The default value may be set by passing an empty value for the option: + +```shell +dokku network:set node-js-app tld +``` + +The `tld` property can also be set globally. The global default is emty string, and the global value is used when no app-specific value is set. + +```shell +dokku network:set --global tld svc.cluster.local +``` + +The default value may be set by passing an empty value for the option. + +```shell +dokku network:set --global tld +``` + #### When to attach containers to a network Containers can be attached to a network for a variety of reasons: @@ -203,8 +241,17 @@ Whatever the reason, the semantics of the two network hooks are important and ar - Container state on attach: `running` - Use case: When another container on the network needs to access _this_ container. - Example: A background process needs to communicate with the web process exposed by this container. +- `initial-network`: + - Phase it applies to: + - `build`: Intermediate containers created during the build process. + - `deploy`: Deployed app containers. + - `run`: Containers created by the `run` command. + - Container state on attach: `created` + - Use case: When another container on the network is already running and needed by this container. + - Example: A key-value store exposing itself to all your apps may be on the `initial-network`. -> Warning: If the attachment fails at this stage, this may result in your application failing to respond to proxied requests once older containers are removed. + +> Warning: If the attachment fails during the `running` container state, this may result in your application failing to respond to proxied requests once older containers are removed. ### Rebuilding network settings @@ -278,6 +325,18 @@ CONTAINER ID IMAGE COMMAND CREATED d6499edb0edb dokku/node-js-app:latest "/bin/bash -c '/star About a minute ago Up About a minute 0.0.0.0:49153->5000/tcp node-js-app.web.1 ``` +The `bind-all-interfaces` property can also be set globally. The global default is `false`, and the global value is used when no app-specific value is set. + +```shell +dokku network:set --global bind-all-interfaces true +``` + +The default value may be set by passing an empty value for the option. + +```shell +dokku network:set --global bind-all-interfaces +``` + ### Displaying network reports for an app You can get a report about the app's network status using the `network:report` command: @@ -288,14 +347,56 @@ dokku network:report ``` =====> node-js-app network information - Network bind all interfaces: false - Network listeners: 172.17.0.1:5000 + Network attach post create: + Network attach post deploy: + Network bind all interfaces: false + Network computed attach post create: + Network computed attach post deploy: + Network computed bind all interfaces:false + Network computed initial network: + Network computed tld: + Network global attach post create: + Network global attach post deploy: + Network global bind all interfaces:false + Network global initial network: + Network global tld: + Network initial network: + Network tld: + Network web listeners: 172.17.0.1:5000 =====> python-sample network information - Network bind all interfaces: false - Network listeners: 172.17.0.2:5000 + Network attach post create: + Network attach post deploy: + Network bind all interfaces: false + Network computed attach post create: + Network computed attach post deploy: + Network computed bind all interfaces:false + Network computed initial network: + Network computed tld: + Network global attach post create: + Network global attach post deploy: + Network global bind all interfaces:false + Network global initial network: + Network global tld: + Network initial network: + Network tld: + Network web listeners: 172.17.0.2:5000 =====> ruby-sample network information - Network bind all interfaces: true - Network listeners: + Network attach post create: + Network attach post deploy: + Network bind all interfaces: false + Network computed attach post create: + Network computed attach post deploy: + Network computed bind all interfaces:false + Network computed initial network: + Network computed tld: + Network global attach post create: + Network global attach post deploy: + Network global bind all interfaces:false + Network global initial network: + Network global tld: + Network initial network: + Network tld: + Network web listeners: ``` You can run the command for a specific app also. @@ -306,8 +407,22 @@ dokku network:report node-js-app ``` =====> node-js-app network information - Network bind all interfaces: false - Network listeners: 172.17.0.1:5000 + Network attach post create: + Network attach post deploy: + Network bind all interfaces: false + Network computed attach post create: + Network computed attach post deploy: + Network computed bind all interfaces:false + Network computed initial network: + Network computed tld: + Network global attach post create: + Network global attach post deploy: + Network global bind all interfaces:false + Network global initial network: + Network global tld: + Network initial network: + Network tld: + Network web listeners: 172.17.0.1:5000 ``` You can pass flags which will output only the value of the specific information you want. For example: diff --git a/plugins/common/.gitignore b/plugins/common/.gitignore index a39ded4ae..100934a87 100644 --- a/plugins/common/.gitignore +++ b/plugins/common/.gitignore @@ -1 +1,6 @@ /prop +/common +/core-post-deploy +/install +/post-delete +/triggers diff --git a/plugins/network/.gitignore b/plugins/network/.gitignore index dee017e8f..efa5c21c7 100644 --- a/plugins/network/.gitignore +++ b/plugins/network/.gitignore @@ -7,3 +7,6 @@ /install /post-* /report +/docker-args-process-build +/docker-args-process-deploy +/docker-args-process-run diff --git a/plugins/network/Makefile b/plugins/network/Makefile index 32a94414d..0c2eff4bb 100644 --- a/plugins/network/Makefile +++ b/plugins/network/Makefile @@ -1,5 +1,5 @@ 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-app-rename-setup triggers/post-container-create triggers/post-create triggers/post-delete triggers/report +TRIGGERS = triggers/core-post-deploy triggers/docker-args-process-build triggers/docker-args-process-deploy triggers/docker-args-process-run 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-app-rename-setup triggers/post-container-create triggers/post-create triggers/post-delete triggers/report BUILD = commands subcommands triggers PLUGIN_NAME = network diff --git a/plugins/network/functions.go b/plugins/network/functions.go index 17d0ee8e8..28badd79c 100644 --- a/plugins/network/functions.go +++ b/plugins/network/functions.go @@ -20,9 +20,7 @@ func attachAppToNetwork(containerID string, networkName string, appName string, } if phase == "deploy" { - property := "tld" - defaultValue := GetDefaultValue(property) - tld := common.PropertyGetDefault("network", appName, property, defaultValue) + tld := reportComputedTld(appName) networkAlias := fmt.Sprintf("%v.%v", appName, processType) if tld != "" { @@ -62,14 +60,13 @@ func isConflictingPropertyValue(appName string, property string, value string) b return false } - otherProperty := "attach-post-create" - if property == otherProperty { - otherProperty = "attach-post-deploy" + otherValue := "" + if property == "attach-post-create" { + otherValue = reportComputedAttachPostDeploy(appName) + } else { + otherValue = reportComputedAttachPostCreate(appName) } - defaultValue := GetDefaultValue(otherProperty) - otherValue := common.PropertyGetDefault("network", appName, otherProperty, defaultValue) - return value == otherValue } diff --git a/plugins/network/network.go b/plugins/network/network.go index 954083f1e..b7d77e8fd 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -17,14 +17,21 @@ import ( var ( // DefaultProperties is a map of all valid network properties with corresponding default property values DefaultProperties = map[string]string{ - "bind-all-interfaces": "false", + "bind-all-interfaces": "", "attach-post-create": "", "attach-post-deploy": "", + "initial-network": "", "tld": "", } // GlobalProperties is a map of all valid global network properties - GlobalProperties = map[string]bool{} + GlobalProperties = map[string]bool{ + "bind-all-interfaces": true, + "attach-post-create": true, + "attach-post-deploy": true, + "initial-network": true, + "tld": true, + } ) // BuildConfig builds network config files @@ -105,9 +112,14 @@ func GetContainerIpaddress(appName, processType, containerID string) (ipAddr str } } - b, err := common.DockerInspect(containerID, "{{.NetworkSettings.Networks.bridge.IPAddress}}") + initialNetwork := reportComputedInitialNetwork(appName) + if initialNetwork == "" { + initialNetwork = "bridge" + } + + b, err := common.DockerInspect(containerID, fmt.Sprintf("{{ $network := index .NetworkSettings.Networks \"%s\" }}{{ $network.IPAddress}}", initialNetwork)) if err != nil || len(b) == 0 { - // docker < 1.9 compatibility + // Deprecated: docker < 1.9 compatibility b, err = common.DockerInspect(containerID, "{{ .NetworkSettings.IPAddress }}") } @@ -151,15 +163,6 @@ func GetContainerPort(appName, processType string, containerID string, isHerokui return } -// GetDefaultValue returns the default value for a given property -func GetDefaultValue(property string) (value string) { - value, ok := DefaultProperties[property] - if ok { - return - } - return -} - // GetListeners returns a string array of app listeners func GetListeners(appName string, processType string) []string { appRoot := common.AppRoot(appName) diff --git a/plugins/network/network_test.go b/plugins/network/network_test.go deleted file mode 100644 index 9aed75050..000000000 --- a/plugins/network/network_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package network - -import ( - "testing" - - . "github.com/onsi/gomega" -) - -func TestNetworkGetDefaultValue(t *testing.T) { - RegisterTestingT(t) - Expect(GetDefaultValue("bind-all-interfaces")).To(Equal("false")) -} diff --git a/plugins/network/report.go b/plugins/network/report.go index 23b876f73..f3c3ed606 100644 --- a/plugins/network/report.go +++ b/plugins/network/report.go @@ -13,10 +13,22 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { } flags := map[string]common.ReportFunc{ - "--network-bind-all-interfaces": reportBindAllInterfaces, - "--network-attach-post-create": reportAttachPostCreate, - "--network-attach-post-deploy": reportAttachPostDeploy, - "--network-web-listeners": reportWebListeners, + "--network-bind-all-interfaces": reportBindAllInterfaces, + "--network-attach-post-create": reportAttachPostCreate, + "--network-attach-post-deploy": reportAttachPostDeploy, + "--network-computed-attach-post-create": reportComputedAttachPostCreate, + "--network-computed-attach-post-deploy": reportComputedAttachPostDeploy, + "--network-computed-bind-all-interfaces": reportComputedBindAllInterfaces, + "--network-computed-initial-network": reportComputedInitialNetwork, + "--network-computed-tld": reportComputedTld, + "--network-global-attach-post-create": reportGlobalAttachPostCreate, + "--network-global-attach-post-deploy": reportGlobalAttachPostDeploy, + "--network-global-bind-all-interfaces": reportGlobalBindAllInterfaces, + "--network-global-initial-network": reportGlobalInitialNetwork, + "--network-global-tld": reportGlobalTld, + "--network-initial-network": reportInitialNetwork, + "--network-tld": reportTld, + "--network-web-listeners": reportWebListeners, } flagKeys := []string{} @@ -30,10 +42,6 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { return common.ReportSingleApp("network", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter) } -func reportBindAllInterfaces(appName string) string { - return common.PropertyGet("network", appName, "bind-all-interfaces") -} - func reportAttachPostCreate(appName string) string { return common.PropertyGet("network", appName, "attach-post-create") } @@ -42,6 +50,83 @@ func reportAttachPostDeploy(appName string) string { return common.PropertyGet("network", appName, "attach-post-deploy") } +func reportBindAllInterfaces(appName string) string { + return common.PropertyGet("network", appName, "bind-all-interfaces") +} + +func reportComputedAttachPostCreate(appName string) string { + value := reportAttachPostCreate(appName) + if value == "" { + value = reportGlobalAttachPostCreate(appName) + } + + return value +} + +func reportComputedAttachPostDeploy(appName string) string { + value := reportAttachPostDeploy(appName) + if value == "" { + value = reportGlobalAttachPostDeploy(appName) + } + + return value +} + +func reportComputedBindAllInterfaces(appName string) string { + value := reportBindAllInterfaces(appName) + if value == "" { + value = reportGlobalBindAllInterfaces(appName) + } + + return value +} + +func reportComputedInitialNetwork(appName string) string { + value := reportInitialNetwork(appName) + if value == "" { + value = reportGlobalInitialNetwork(appName) + } + + return value +} + +func reportComputedTld(appName string) string { + value := reportTld(appName) + if value == "" { + value = reportGlobalTld(appName) + } + + return value +} + +func reportGlobalAttachPostCreate(appName string) string { + return common.PropertyGet("network", "--global", "attach-post-create") +} + +func reportGlobalAttachPostDeploy(appName string) string { + return common.PropertyGet("network", "--global", "attach-post-deploy") +} + +func reportGlobalBindAllInterfaces(appName string) string { + return common.PropertyGetDefault("network", "--global", "bind-all-interfaces", "false") +} + +func reportGlobalInitialNetwork(appName string) string { + return common.PropertyGet("network", "--global", "initial-network") +} + +func reportGlobalTld(appName string) string { + return common.PropertyGet("network", "--global", "tld") +} + +func reportInitialNetwork(appName string) string { + return common.PropertyGet("network", appName, "initial-network") +} + +func reportTld(appName string) string { + return common.PropertyGet("network", appName, "tld") +} + func reportWebListeners(appName string) string { return strings.Join(GetListeners(appName, "web"), " ") } diff --git a/plugins/network/src/triggers/triggers.go b/plugins/network/src/triggers/triggers.go index 0ce27d43a..625180f24 100644 --- a/plugins/network/src/triggers/triggers.go +++ b/plugins/network/src/triggers/triggers.go @@ -18,6 +18,15 @@ func main() { var err error switch trigger { + case "docker-args-process-build": + appName := flag.Arg(0) + err = network.TriggerDockerArgsProcess(appName) + case "docker-args-process-deploy": + appName := flag.Arg(0) + err = network.TriggerDockerArgsProcess(appName) + case "docker-args-process-run": + appName := flag.Arg(0) + err = network.TriggerDockerArgsProcess(appName) case "install": err = network.TriggerInstall() case "network-build-config": diff --git a/plugins/network/triggers.go b/plugins/network/triggers.go index 23b39bf02..95c952d23 100644 --- a/plugins/network/triggers.go +++ b/plugins/network/triggers.go @@ -2,6 +2,7 @@ package network import ( "fmt" + "io/ioutil" "os" "strings" "unicode/utf8" @@ -10,6 +11,22 @@ import ( "github.com/dokku/dokku/plugins/config" ) +// TriggerDockerArgsProcess outputs the network plugin docker options for an app +func TriggerDockerArgsProcess(appName string) error { + stdin, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return err + } + + initialNetwork := reportComputedInitialNetwork(appName) + if initialNetwork != "" { + fmt.Printf(" --network=%s ", initialNetwork) + } + + fmt.Print(string(stdin)) + return nil +} + // TriggerInstall runs the install step for the network plugin func TriggerInstall() error { if err := common.PropertySetup("network"); err != nil { @@ -106,9 +123,20 @@ func TriggerNetworkGetPort(appName string, processType string, containerID strin // TriggerNetworkGetProperty writes the network property to stdout for a given app container func TriggerNetworkGetProperty(appName string, property string) error { - defaultValue := GetDefaultValue(property) - value := common.PropertyGetDefault("network", appName, property, defaultValue) - fmt.Println(value) + computedValueMap := map[string]common.ReportFunc{ + "attach-post-create": reportComputedAttachPostCreate, + "attach-post-deploy": reportComputedAttachPostDeploy, + "bind-all-interfaces": reportComputedBindAllInterfaces, + "initial-network": reportComputedInitialNetwork, + "tld": reportComputedTld, + } + + fn, ok := computedValueMap[property] + if !ok { + return fmt.Errorf("Invalid network property specified: %v", property) + } + + fmt.Println(fn(appName)) return nil } @@ -190,9 +218,7 @@ func TriggerPostContainerCreate(containerType string, containerID string, appNam } - property := "attach-post-create" - defaultValue := GetDefaultValue(property) - networkName := common.PropertyGetDefault("network", appName, property, defaultValue) + networkName := reportComputedAttachPostCreate(appName) if networkName == "" { return nil @@ -232,9 +258,7 @@ func TriggerPostDelete(appName string) error { // TriggerCorePostDeploy associates the container with a specified network func TriggerCorePostDeploy(appName string) error { - property := "attach-post-deploy" - defaultValue := GetDefaultValue(property) - networkName := common.PropertyGetDefault("network", appName, property, defaultValue) + networkName := reportComputedAttachPostDeploy(appName) if networkName == "" { return nil } diff --git a/tests/unit/network.bats b/tests/unit/network.bats index 4eddaf925..596699448 100644 --- a/tests/unit/network.bats +++ b/tests/unit/network.bats @@ -15,6 +15,7 @@ teardown() { [[ -f "$DOKKU_ROOT/HOSTNAME.bak" ]] && mv "$DOKKU_ROOT/HOSTNAME.bak" "$DOKKU_ROOT/HOSTNAME" && chown dokku:dokku "$DOKKU_ROOT/HOSTNAME" docker network rm create-network || true docker network rm deploy-network || true + docker network rm initial-network || true global_teardown } @@ -162,6 +163,11 @@ teardown() { echo "status: $status" assert_success + run /bin/bash -c "dokku network:create initial-network" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku network:set $TEST_APP attach-post-create nonexistent-network" echo "output: $output" echo "status: $status" @@ -193,6 +199,11 @@ teardown() { echo "status: $status" assert_success + run /bin/bash -c "dokku network:set $TEST_APP initial-network initial-network" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku ps:rebuild $TEST_APP" echo "output: $output" echo "status: $status" @@ -204,6 +215,11 @@ teardown() { echo "status: $status" assert_failure + run /bin/bash -c "dokku --force network:destroy initial-network" + echo "output: $output" + echo "status: $status" + assert_failure + run /bin/bash -c "dokku --force apps:destroy $TEST_APP" echo "output: $output" echo "status: $status" @@ -221,4 +237,9 @@ teardown() { echo "output: $output" echo "status: $status" assert_success + + run /bin/bash -c "dokku --force network:destroy initial-network" + echo "output: $output" + echo "status: $status" + assert_success }