From d7ab57bed039e540eefc4fb6becda641ce57354c Mon Sep 17 00:00:00 2001
From: matthuisman <me@matthuisman.com>
Date: Fri, 7 Nov 2014 13:35:40 +1300
Subject: [PATCH 1/2] Add initial inherit/parent code

---
 fig/project.py | 16 ++++++++++++++--
 fig/service.py | 21 ++++++++++++++++++++-
 2 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/fig/project.py b/fig/project.py
index 569df38d580..063fde1ddbe 100644
--- a/fig/project.py
+++ b/fig/project.py
@@ -27,7 +27,7 @@ def visit(n):
                 raise DependencyError('Circular import between %s' % ' and '.join(temporary_marked))
         if n in unmarked:
             temporary_marked.add(n['name'])
-            dependents = [m for m in services if (n['name'] in get_service_names(m.get('links', []))) or (n['name'] in m.get('volumes_from', []))]
+            dependents = [m for m in services if (n['name'] in get_service_names(m.get('links', []))) or (n['name'] in m.get('volumes_from', [])) or (n['name'] == m.get('inherits', ''))]
             for m in dependents:
                 visit(m)
             temporary_marked.remove(n['name'])
@@ -58,8 +58,9 @@ def from_dicts(cls, name, service_dicts, client):
         for service_dict in sort_service_dicts(service_dicts):
             links = project.get_links(service_dict)
             volumes_from = project.get_volumes_from(service_dict)
+            parent = project.get_parent(service_dict)
 
-            project.services.append(Service(client=client, project=name, links=links, volumes_from=volumes_from, **service_dict))
+            project.services.append(Service(client=client, project=name, links=links, volumes_from=volumes_from, parent=parent, **service_dict))
         return project
 
     @classmethod
@@ -113,6 +114,17 @@ def get_services(self, service_names=None, include_links=False):
             [uniques.append(s) for s in services if s not in uniques]
             return uniques
 
+    def get_parent(self, service_dict):
+        parent = None
+        if 'inherits' in service_dict:
+            parent_name = service_dict.get('inherits', None)
+            try:
+                parent = self.get_service(parent_name)
+            except NoSuchService:
+                raise ConfigurationError('Service "%s" inherits service "%s" which does not exist.' % (service_dict['name'], parent_name))
+            del service_dict['inherits']
+        return parent
+
     def get_links(self, service_dict):
         links = []
         if 'links' in service_dict:
diff --git a/fig/service.py b/fig/service.py
index e6dcf01281b..950f7136921 100644
--- a/fig/service.py
+++ b/fig/service.py
@@ -14,6 +14,7 @@
 
 log = logging.getLogger(__name__)
 
+DONT_INHERIT = ['ports', 'name', 'command']
 
 DOCKER_CONFIG_KEYS = ['image', 'command', 'hostname', 'domainname', 'user', 'detach', 'stdin_open', 'tty', 'mem_limit', 'ports', 'environment', 'dns', 'volumes', 'entrypoint', 'privileged', 'volumes_from', 'net', 'working_dir']
 DOCKER_CONFIG_HINTS = {
@@ -50,7 +51,7 @@ class ConfigError(ValueError):
 
 
 class Service(object):
-    def __init__(self, name, client=None, project='default', links=None, volumes_from=None, **options):
+    def __init__(self, name, client=None, project='default', links=None, volumes_from=None, parent=None, **options):
         if not re.match('^%s+$' % VALID_NAME_CHARS, name):
             raise ConfigError('Invalid service name "%s" - only %s are allowed' % (name, VALID_NAME_CHARS))
         if not re.match('^%s+$' % VALID_NAME_CHARS, project):
@@ -74,6 +75,24 @@ def __init__(self, name, client=None, project='default', links=None, volumes_fro
         self.volumes_from = volumes_from or []
         self.options = options
 
+        self.inherit(parent)
+
+    def inherit(self, parent):
+        if parent == None:
+            return
+
+        self.links.extend(x for x in parent.links if x not in self.links)
+        self.volumes_from.extend(x for x in parent.volumes_from if x not in self.volumes_from)
+
+        for option in parent.options:
+            if option not in DONT_INHERIT:
+                if isinstance(parent.options[option], list):
+                    self.options[option] = self.options.get(option, []) + parent.options[option]
+                elif isinstance(parent.options[option], dict):
+                    self.options[option] = dict(parent.options[option].items() + self.options.get(option, {}).items())
+                elif option not in self.options:
+                    self.options[option] = parent.options[option]
+
     def containers(self, stopped=False, one_off=False):
         return [Container.from_ps(self.client, container)
                 for container in self.client.containers(all=stopped)

From 0b6e5ae9e34c4ad78470c914c9b302ed1bdcb331 Mon Sep 17 00:00:00 2001
From: matthuisman <me@matthuisman.com>
Date: Fri, 7 Nov 2014 13:50:09 +1300
Subject: [PATCH 2/2] Add initial inherit/parent code

Signed-off-by: Matt Huisman <me@matthuisman.com>
---
 fig/project.py | 16 ++++++++++++++--
 fig/service.py | 21 ++++++++++++++++++++-
 2 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/fig/project.py b/fig/project.py
index 569df38d580..063fde1ddbe 100644
--- a/fig/project.py
+++ b/fig/project.py
@@ -27,7 +27,7 @@ def visit(n):
                 raise DependencyError('Circular import between %s' % ' and '.join(temporary_marked))
         if n in unmarked:
             temporary_marked.add(n['name'])
-            dependents = [m for m in services if (n['name'] in get_service_names(m.get('links', []))) or (n['name'] in m.get('volumes_from', []))]
+            dependents = [m for m in services if (n['name'] in get_service_names(m.get('links', []))) or (n['name'] in m.get('volumes_from', [])) or (n['name'] == m.get('inherits', ''))]
             for m in dependents:
                 visit(m)
             temporary_marked.remove(n['name'])
@@ -58,8 +58,9 @@ def from_dicts(cls, name, service_dicts, client):
         for service_dict in sort_service_dicts(service_dicts):
             links = project.get_links(service_dict)
             volumes_from = project.get_volumes_from(service_dict)
+            parent = project.get_parent(service_dict)
 
-            project.services.append(Service(client=client, project=name, links=links, volumes_from=volumes_from, **service_dict))
+            project.services.append(Service(client=client, project=name, links=links, volumes_from=volumes_from, parent=parent, **service_dict))
         return project
 
     @classmethod
@@ -113,6 +114,17 @@ def get_services(self, service_names=None, include_links=False):
             [uniques.append(s) for s in services if s not in uniques]
             return uniques
 
+    def get_parent(self, service_dict):
+        parent = None
+        if 'inherits' in service_dict:
+            parent_name = service_dict.get('inherits', None)
+            try:
+                parent = self.get_service(parent_name)
+            except NoSuchService:
+                raise ConfigurationError('Service "%s" inherits service "%s" which does not exist.' % (service_dict['name'], parent_name))
+            del service_dict['inherits']
+        return parent
+
     def get_links(self, service_dict):
         links = []
         if 'links' in service_dict:
diff --git a/fig/service.py b/fig/service.py
index e6dcf01281b..950f7136921 100644
--- a/fig/service.py
+++ b/fig/service.py
@@ -14,6 +14,7 @@
 
 log = logging.getLogger(__name__)
 
+DONT_INHERIT = ['ports', 'name', 'command']
 
 DOCKER_CONFIG_KEYS = ['image', 'command', 'hostname', 'domainname', 'user', 'detach', 'stdin_open', 'tty', 'mem_limit', 'ports', 'environment', 'dns', 'volumes', 'entrypoint', 'privileged', 'volumes_from', 'net', 'working_dir']
 DOCKER_CONFIG_HINTS = {
@@ -50,7 +51,7 @@ class ConfigError(ValueError):
 
 
 class Service(object):
-    def __init__(self, name, client=None, project='default', links=None, volumes_from=None, **options):
+    def __init__(self, name, client=None, project='default', links=None, volumes_from=None, parent=None, **options):
         if not re.match('^%s+$' % VALID_NAME_CHARS, name):
             raise ConfigError('Invalid service name "%s" - only %s are allowed' % (name, VALID_NAME_CHARS))
         if not re.match('^%s+$' % VALID_NAME_CHARS, project):
@@ -74,6 +75,24 @@ def __init__(self, name, client=None, project='default', links=None, volumes_fro
         self.volumes_from = volumes_from or []
         self.options = options
 
+        self.inherit(parent)
+
+    def inherit(self, parent):
+        if parent == None:
+            return
+
+        self.links.extend(x for x in parent.links if x not in self.links)
+        self.volumes_from.extend(x for x in parent.volumes_from if x not in self.volumes_from)
+
+        for option in parent.options:
+            if option not in DONT_INHERIT:
+                if isinstance(parent.options[option], list):
+                    self.options[option] = self.options.get(option, []) + parent.options[option]
+                elif isinstance(parent.options[option], dict):
+                    self.options[option] = dict(parent.options[option].items() + self.options.get(option, {}).items())
+                elif option not in self.options:
+                    self.options[option] = parent.options[option]
+
     def containers(self, stopped=False, one_off=False):
         return [Container.from_ps(self.client, container)
                 for container in self.client.containers(all=stopped)