mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-19 12:16:30 +00:00
Create private link via iptables to linked containers
Allow active links to be removed
This commit is contained in:
@@ -960,10 +960,42 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
|
||||
func getLinksJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
out := []APILink{}
|
||||
name := r.FormValue("name")
|
||||
rawRm := r.FormValue("rm")
|
||||
|
||||
links := srv.runtime.links.Get(name)
|
||||
rm, err := getBoolParam(rawRm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rm {
|
||||
link := srv.runtime.links.GetById(name)
|
||||
if link != nil {
|
||||
if err := srv.runtime.links.removeLink(link); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return nil
|
||||
}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return nil
|
||||
}
|
||||
if name == "" {
|
||||
return fmt.Errorf("Name cannot be empty for link")
|
||||
}
|
||||
|
||||
container := srv.runtime.Get(name)
|
||||
if container == nil {
|
||||
return fmt.Errorf("Container not found %s", name)
|
||||
}
|
||||
links := srv.runtime.links.Get(container)
|
||||
for _, l := range links {
|
||||
out = append(out, APILink{l.To, l.From, l.IP, fmt.Sprint(l.Port), l.Alias})
|
||||
out = append(out, APILink{
|
||||
ID: l.ID(),
|
||||
To: l.ToID,
|
||||
From: l.FromID,
|
||||
Port: fmt.Sprint(l.Port),
|
||||
Alias: l.Alias,
|
||||
})
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
+1
-1
@@ -122,9 +122,9 @@ type APICopy struct {
|
||||
}
|
||||
|
||||
type APILink struct {
|
||||
ID string
|
||||
To string
|
||||
From string
|
||||
IP string
|
||||
Port string
|
||||
Alias string
|
||||
}
|
||||
|
||||
+13
-3
@@ -1114,6 +1114,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
|
||||
func (cli *DockerCli) CmdLink(args ...string) error {
|
||||
cmd := Subcmd("link", "[OPTIONS] CONTAINER", "Get the links for a container")
|
||||
flRm := cmd.Bool("rm", false, "Remove an existing link by the link ID")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return err
|
||||
@@ -1122,21 +1123,30 @@ func (cli *DockerCli) CmdLink(args ...string) error {
|
||||
v := url.Values{}
|
||||
v.Set("name", cmd.Arg(0))
|
||||
|
||||
body, _, err := cli.call("GET", "/links/json?"+v.Encode(), nil)
|
||||
if *flRm {
|
||||
v.Set("rm", "1")
|
||||
}
|
||||
|
||||
body, statusCode, err := cli.call("GET", "/links/json?"+v.Encode(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *flRm && statusCode == 200 {
|
||||
fmt.Printf("Link successfully removed: %s\n", cmd.Arg(0))
|
||||
return nil
|
||||
}
|
||||
|
||||
var links []APILink
|
||||
if err := json.Unmarshal(body, &links); err != nil {
|
||||
return err
|
||||
}
|
||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
||||
|
||||
fmt.Fprintf(w, "FROM\tTO\tADDRESS\tALIAS")
|
||||
fmt.Fprintf(w, "ID\tFROM\tTO\tPORT\tALIAS")
|
||||
fmt.Fprintf(w, "\n")
|
||||
for _, l := range links {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s", l.From, l.To, fmt.Sprintf("%s:%s", l.IP, l.Port), l.Alias)
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s", l.ID, l.From, l.To, l.Port, l.Alias)
|
||||
fmt.Fprintf(w, "\n")
|
||||
}
|
||||
w.Flush()
|
||||
|
||||
+27
-35
@@ -91,7 +91,7 @@ type HostConfig struct {
|
||||
ContainerIDFile string
|
||||
LxcConf []KeyValuePair
|
||||
PortBindings map[Port][]PortBinding
|
||||
Links []Link
|
||||
Links []string
|
||||
}
|
||||
|
||||
type BindMap struct {
|
||||
@@ -265,11 +265,6 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
}
|
||||
}
|
||||
|
||||
links, err := parseLinks(flLinks)
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
Hostname: *flHostname,
|
||||
Domainname: domainname,
|
||||
@@ -300,7 +295,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
||||
ContainerIDFile: *flContainerIDFile,
|
||||
LxcConf: lxcConf,
|
||||
PortBindings: portBindings,
|
||||
Links: links,
|
||||
Links: flLinks,
|
||||
}
|
||||
|
||||
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
|
||||
@@ -827,21 +822,31 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
||||
"-e", "HOSTNAME="+container.Config.Hostname,
|
||||
)
|
||||
|
||||
if hostConfig != nil && hostConfig.Links != nil {
|
||||
if !container.Config.NetworkDisabled && hostConfig != nil && hostConfig.Links != nil {
|
||||
runtime := container.runtime
|
||||
for _, l := range hostConfig.Links {
|
||||
linkedContainer := runtime.Get(l.From)
|
||||
parts, err := parseLink(l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := NewPort(splitProtoPort(parts["port"]))
|
||||
linkedContainer := runtime.Get(parts["id"])
|
||||
|
||||
if linkedContainer == nil {
|
||||
return fmt.Errorf("Cannot locate container for link: %s AS %s", l.From, l.Alias)
|
||||
}
|
||||
if err := linkedContainer.AcceptLink(l); err != nil {
|
||||
link, err := runtime.links.NewLink(container, linkedContainer, runtime.networkManager.bridgeIface, p, parts["alias"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Link(linkedContainer, &l); err != nil {
|
||||
|
||||
if err := link.Enable(); err != nil {
|
||||
// If we encounter an err, make sure we remove all links
|
||||
for _, registeredLinks := range runtime.links.Get(container) {
|
||||
runtime.links.removeLink(registeredLinks)
|
||||
}
|
||||
return err
|
||||
}
|
||||
params = append(params, "-e", l.ToEnv())
|
||||
for _, envVar := range link.ToEnv() {
|
||||
params = append(params, "-e", envVar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1080,6 +1085,13 @@ func (container *Container) monitor(hostConfig *HostConfig) {
|
||||
|
||||
// Cleanup
|
||||
container.releaseNetwork()
|
||||
|
||||
//Destroy all links
|
||||
runtime := container.runtime
|
||||
for _, link := range runtime.links.Get(container) {
|
||||
runtime.links.removeLink(link)
|
||||
}
|
||||
|
||||
if container.Config.OpenStdin {
|
||||
if err := container.stdin.Close(); err != nil {
|
||||
utils.Debugf("%s: Error close stdin: %s", container.ID, err)
|
||||
@@ -1370,26 +1382,6 @@ func (container *Container) Copy(resource string) (Archive, error) {
|
||||
return TarFilter(basePath, Uncompressed, filter)
|
||||
}
|
||||
|
||||
func (container *Container) AcceptLink(l Link) error {
|
||||
if !container.State.Running {
|
||||
return fmt.Errorf("Cannot accept link on a non running container: %s AS %s", l.From, l.Alias)
|
||||
}
|
||||
if !container.Exposes(l.Port) {
|
||||
return fmt.Errorf("Cannot accept link to %s because %s is not exposed", container.ID, l.Port)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) Link(c *Container, l *Link) error {
|
||||
l.To = utils.TruncateID(container.ID)
|
||||
l.IP = c.NetworkSettings.IPAddress
|
||||
|
||||
if err := container.runtime.links.RegisterLink(*l); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns true if the container exposes a certain port
|
||||
func (container *Container) Exposes(p Port) bool {
|
||||
_, exists := container.Config.ExposedPorts[p]
|
||||
|
||||
@@ -2,58 +2,147 @@ package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/iptables"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A Link represents a connection between two containers
|
||||
// for a specific port on a specific bridge interface
|
||||
type Link struct {
|
||||
From string
|
||||
To string
|
||||
IP string
|
||||
Port Port
|
||||
Alias string
|
||||
FromID string
|
||||
ToID string
|
||||
FromIP string
|
||||
ToIP string
|
||||
BridgeInterface string
|
||||
Port Port
|
||||
Alias string
|
||||
FromEnvironment []string
|
||||
isEnabled bool
|
||||
}
|
||||
|
||||
type LinkRepository struct {
|
||||
links map[string]Link
|
||||
links map[string]*Link
|
||||
}
|
||||
|
||||
func (l *Link) ToEnv() string {
|
||||
return fmt.Sprintf("%s_ADDR=%s://%s:%s", strings.ToUpper(l.Alias), l.Port.Proto(), l.IP, l.Port.Port())
|
||||
func (r *LinkRepository) NewLink(to, from *Container, bridgeInterface string, p Port, alias string) (*Link, error) {
|
||||
if !from.State.Running {
|
||||
return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", from.ID, alias)
|
||||
}
|
||||
if !from.Exposes(p) {
|
||||
return nil, fmt.Errorf("Cannot link to %s because %s is not exposed", from.ID, p)
|
||||
}
|
||||
l := &Link{
|
||||
FromID: utils.TruncateID(from.ID),
|
||||
ToID: utils.TruncateID(to.ID),
|
||||
BridgeInterface: bridgeInterface,
|
||||
Alias: alias,
|
||||
Port: p,
|
||||
FromIP: from.NetworkSettings.IPAddress,
|
||||
ToIP: to.NetworkSettings.IPAddress,
|
||||
FromEnvironment: from.Config.Env,
|
||||
}
|
||||
if err := r.registerLink(l); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *Link) ID() string {
|
||||
return fmt.Sprintf("%s:%s", l.ToID, l.Alias)
|
||||
}
|
||||
|
||||
func (l *Link) ToEnv() []string {
|
||||
env := []string{fmt.Sprintf("%s_ADDR=%s://%s:%s", strings.ToUpper(l.Alias), l.Port.Proto(), l.FromIP, l.Port.Port())}
|
||||
if l.FromEnvironment != nil {
|
||||
for _, v := range l.FromEnvironment {
|
||||
parts := strings.Split(v, "=")
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
env = append(env, fmt.Sprintf("%s_ENV_%s=%s", strings.ToUpper(l.Alias), parts[0], parts[1]))
|
||||
}
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
func (l *Link) Enable() error {
|
||||
if err := l.toggle("-I"); err != nil {
|
||||
return err
|
||||
}
|
||||
l.isEnabled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Link) Disable() {
|
||||
// We do not care about erros here because the link may not
|
||||
// exist in iptables
|
||||
l.toggle("-D")
|
||||
|
||||
l.isEnabled = false
|
||||
}
|
||||
|
||||
func (l *Link) toggle(action string) error {
|
||||
if err := iptables.Raw(action, "FORWARD",
|
||||
"-i", l.BridgeInterface, "-o", l.BridgeInterface,
|
||||
"-p", l.Port.Proto(),
|
||||
"-s", l.ToIP,
|
||||
"--dport", l.Port.Port(),
|
||||
"-d", l.FromIP,
|
||||
"-j", "ACCEPT"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := iptables.Raw(action, "FORWARD",
|
||||
"-i", l.BridgeInterface, "-o", l.BridgeInterface,
|
||||
"-p", l.Port.Proto(),
|
||||
"-s", l.FromIP,
|
||||
"--sport", l.Port.Port(),
|
||||
"-d", l.ToIP,
|
||||
"-j", "ACCEPT"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewLinkRepository(root string) (*LinkRepository, error) {
|
||||
r := &LinkRepository{make(map[string]Link)}
|
||||
r := &LinkRepository{make(map[string]*Link)}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Return all links for a container
|
||||
func (l *LinkRepository) Get(id string) []Link {
|
||||
id = strings.Trim(strings.ToLower(id), "")
|
||||
out := []Link{}
|
||||
func (l *LinkRepository) Get(c *Container) []*Link {
|
||||
id := utils.TruncateID(c.ID)
|
||||
out := []*Link{}
|
||||
for _, link := range l.links {
|
||||
if link.To == id || link.From == id {
|
||||
if link.ToID == id || link.FromID == id {
|
||||
out = append(out, link)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Returns the link for a current alias
|
||||
func (l *LinkRepository) GetByAlias(alias string) (Link, error) {
|
||||
link, exists := l.links[alias]
|
||||
if !exists {
|
||||
return link, fmt.Errorf("Link does not exist for alias: %s", alias)
|
||||
}
|
||||
return link, nil
|
||||
// Get a link based on the link's ID
|
||||
func (l *LinkRepository) GetById(id string) *Link {
|
||||
return l.links[id]
|
||||
}
|
||||
|
||||
// Create a new link with a unique alias
|
||||
func (l *LinkRepository) RegisterLink(link Link) error {
|
||||
if _, exists := l.links[link.Alias]; exists {
|
||||
return fmt.Errorf("A link for %s already exists", link.Alias)
|
||||
func (l *LinkRepository) registerLink(link *Link) error {
|
||||
if _, exists := l.links[link.ID()]; exists {
|
||||
return fmt.Errorf("A link for %s already exists", link.ID())
|
||||
}
|
||||
utils.Debugf("Registering link: %v", link)
|
||||
l.links[link.Alias] = link
|
||||
utils.Debugf("Registering link: %s", link.ID())
|
||||
l.links[link.ID()] = link
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disable and remote the link from the repository
|
||||
func (l *LinkRepository) removeLink(link *Link) error {
|
||||
link.Disable()
|
||||
|
||||
utils.Debugf("Removing link: %s", link.ID())
|
||||
delete(l.links, link.ID())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -285,28 +285,6 @@ func migratePortMappings(config *Config) error {
|
||||
|
||||
// Links come in the format of
|
||||
// id:port:alias
|
||||
func parseLink(rawLink string) (Link, error) {
|
||||
parts, err := utils.PartParser("id:port:alias", rawLink)
|
||||
if err != nil {
|
||||
return Link{}, err
|
||||
}
|
||||
port := NewPort(splitProtoPort(parts["port"]))
|
||||
|
||||
return Link{
|
||||
From: parts["id"],
|
||||
Alias: parts["alias"],
|
||||
Port: port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseLinks(rawLinks []string) ([]Link, error) {
|
||||
out := make([]Link, len(rawLinks))
|
||||
for i, l := range rawLinks {
|
||||
link, err := parseLink(l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out[i] = link
|
||||
}
|
||||
return out, nil
|
||||
func parseLink(rawLink string) (map[string]string, error) {
|
||||
return utils.PartParser("id:port:alias", rawLink)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user