Blob Blame History Raw
diff --git a/docs/configuration_file.md b/docs/configuration_file.md
index 3ea34af..5876c9a 100644
--- a/docs/configuration_file.md
+++ b/docs/configuration_file.md
@@ -110,3 +110,53 @@ Some options are also mandatory.
 * `memory_limit` (*optional*, `string`) — memory limit to apply to build (for more info, see [documentation for resources](https://github.com/projectatomic/osbs-client/blob/master/docs/resource.md)
 
 * `storage_limit` (*optional*, `string`) — storage limit to apply to build (for more info, see [documentation for resources](https://github.com/projectatomic/osbs-client/blob/master/docs/resource.md)
+
+
+## Build JSON Templates
+
+In the `build_json_dir` there must be `prod.json` and `prod_inner.json` which
+defines the [OpenShift Build](https://docs.openshift.org/latest/dev_guide/builds.html)
+specification that will be used to enable the specific
+[atomic-reactor](https://github.com/projectatomic/atomic-reactor) plugins that
+should be enabled and the config values for each.
+
+There is also a third file that is optional that can exist along side the
+previous two in `build_json_dir` which is `prod_customize.json` and it will
+provide the ability to set site-specific customizations such as removing,
+plugins, adding plugins, or overriding arguments passed to existing plugins.
+
+The syntax of `prod_customize.json` is as follows:
+
+```json
+{
+    "disable_plugins": [
+        {
+            "plugin_type": "foo_type",
+            "plugin_name": "foo_name"
+        },
+        {
+            "plugin_type": "bar_type",
+            "plugin_name": "bar_name"
+        }
+    ],
+
+    "enable_plugins": [
+        {
+            "plugin_type": "foo_type",
+            "plugin_name": "foo_name",
+            "plugin_args": {
+                "foo_arg1": "foo_value1",
+                "foo_arg2": "foo_value2"
+            }
+        }
+    ]
+}
+```
+
+Such that:
+
+* `disable_plugins` will define a list of lists that define the plugin type of the plugin that is to be removed (`prebuild_plugins`, `prepublish_plugins`, `postbuild_plugins`, `exit_plugins`) and the name of the plugin.
+
+* `enable_plugins` is used to add plugins or modify already enabled plugins by overriding args passed to the plugin, these must be defined as key-value pairs as illustrated above. It should be noted that plugins added here will be executed at the end of the list of plugins in that particular `plugin_type` (`prebuild_plugins`, `prepublish_plugins`, `postbuild_plugins`, `exit_plugins`).
+
+
diff --git a/inputs/prod_customize.json b/inputs/prod_customize.json
new file mode 100644
index 0000000..e874211
--- /dev/null
+++ b/inputs/prod_customize.json
@@ -0,0 +1,7 @@
+{
+    "disable_plugins": [
+    ],
+
+    "enable_plugins": [
+    ]
+}
diff --git a/osbs/build/build_request.py b/osbs/build/build_request.py
index a890d0b..aeceeaa 100644
--- a/osbs/build/build_request.py
+++ b/osbs/build/build_request.py
@@ -24,7 +24,7 @@ except ImportError:
 
 from osbs.build.manipulate import DockJsonManipulator
 from osbs.build.spec import BuildSpec
-from osbs.constants import SECRETS_PATH, DEFAULT_OUTER_TEMPLATE, DEFAULT_INNER_TEMPLATE
+from osbs.constants import SECRETS_PATH, DEFAULT_OUTER_TEMPLATE, DEFAULT_INNER_TEMPLATE, DEFAULT_CUSTOMIZE_CONF
 from osbs.exceptions import OsbsException, OsbsValidationException
 from osbs.utils import looks_like_git_hash, git_repo_humanish_part_from_uri
 
@@ -46,6 +46,7 @@ class BuildRequest(object):
         self.build_json = None       # rendered template
         self._template = None        # template loaded from filesystem
         self._inner_template = None  # dock json
+        self._customize_conf = None  # site customize conf for _inner_template
         self._dj = None
         self._resource_limits = None
         self._openshift_required_version = parse_version('1.0.6')
@@ -136,6 +137,20 @@ class BuildRequest(object):
         return self._inner_template
 
     @property
+    def customize_conf(self):
+        if self._customize_conf is None:
+            path = os.path.join(self.build_json_store, DEFAULT_CUSTOMIZE_CONF)
+            logger.debug("loading customize conf from path %s", path)
+            try:
+                with open(path, "r") as fp:
+                    self._customize_conf= json.load(fp)
+            except IOError:
+                # File not found, which is perfectly fine. Set to empty string
+                self._customize_conf = {}
+
+        return self._customize_conf
+
+    @property
     def dj(self):
         if self._dj is None:
             self._dj = DockJsonManipulator(self.template, self.inner_template)
@@ -649,10 +664,60 @@ class BuildRequest(object):
                 self.dj.dock_json_set_arg('postbuild_plugins', 'import_image',
                                           'insecure_registry', True)
 
+    def render_customizations(self):
+        """
+        Customize prod_inner for site specific customizations
+        """
+
+        disable_plugins = self.customize_conf.get('disable_plugins', [])
+        if not disable_plugins:
+            logger.debug("No site-specific plugins to disable")
+        else:
+            for plugin_dict in disable_plugins:
+                try:
+                    self.dj.remove_plugin(
+                        plugin_dict['plugin_type'],
+                        plugin_dict['plugin_name']
+                    )
+                    logger.debug(
+                        "site-specific plugin disabled -> Type:{0} Name:{1}".format(
+                            plugin_dict['plugin_type'],
+                            plugin_dict['plugin_name']
+                        )
+                    )
+                except KeyError:
+                    # Malformed config
+                    logger.debug("Invalid custom configuration found for disable_plugins")
+
+        enable_plugins = self.customize_conf.get('enable_plugins', [])
+        if not enable_plugins:
+            logger.debug("No site-specific plugins to enable")
+        else:
+            for plugin_dict in enable_plugins:
+                try:
+                    self.dj.add_plugin(
+                        plugin_dict['plugin_type'],
+                        plugin_dict['plugin_name'],
+                        plugin_dict['plugin_args']
+                    )
+                    logger.debug(
+                        "site-specific plugin enabled -> Type:{0} Name:{1} Args: {2}".format(
+                            plugin_dict['plugin_type'],
+                            plugin_dict['plugin_name'],
+                            plugin_dict['plugin_args']
+                        )
+                    )
+                except KeyError:
+                    # Malformed config
+                    logger.debug("Invalid custom configuration found for enable_plugins")
+
+
     def render(self, validate=True):
         if validate:
             self.spec.validate()
 
+        self.render_customizations()
+
         # !IMPORTANT! can't be too long: https://github.com/openshift/origin/issues/733
         self.template['metadata']['name'] = self.spec.name.value
         self.render_resource_limits()
diff --git a/osbs/build/manipulate.py b/osbs/build/manipulate.py
index a6b64cf..a0fd1b8 100644
--- a/osbs/build/manipulate.py
+++ b/osbs/build/manipulate.py
@@ -50,6 +50,21 @@ class DockJsonManipulator(object):
                 self.dock_json[plugin_type].remove(p)
                 break
 
+    def add_plugin(self, plugin_type, plugin_name, args_dict):
+        """
+        if config has plugin, override it, else add it
+        """
+
+        plugin_modified = False
+
+        for plugin in self.dock_json[plugin_type]:
+            if plugin['name'] == plugin_name:
+                plugin['args'] = args_dict
+                plugin_modified = True
+
+        if not plugin_modified:
+            self.dock_json[plugin_type].append({"name": plugin_name, "args": args_dict})
+
     def dock_json_has_plugin_conf(self, plugin_type, plugin_name):
         """
         Check whether a plugin is configured.
diff --git a/osbs/constants.py b/osbs/constants.py
index 649626c..282f002 100644
--- a/osbs/constants.py
+++ b/osbs/constants.py
@@ -18,6 +18,7 @@ DEFAULT_CONFIGURATION_FILE = "/etc/osbs.conf"
 DEFAULT_CONFIGURATION_SECTION = "default"
 DEFAULT_OUTER_TEMPLATE = "prod.json"
 DEFAULT_INNER_TEMPLATE = "prod_inner.json"
+DEFAULT_CUSTOMIZE_CONF = "prod_customize.json"
 GENERAL_CONFIGURATION_SECTION = "general"
 POD_FINISHED_STATES = ["failed", "succeeded"]
 POD_FAILED_STATES = ["failed"]
diff --git a/tests/build/test_build_request.py b/tests/build/test_build_request.py
index add97d3..96c642e 100644
--- a/tests/build/test_build_request.py
+++ b/tests/build/test_build_request.py
@@ -1348,3 +1348,97 @@ class TestBuildRequest(object):
         pull_base_image_plugin = get_plugin(
             plugins, 'prebuild_plugins', 'pull_base_image')
         assert pull_base_image_plugin is not None
+
+    def test_render_prod_custom_site_plugin_enable(self):
+        """
+        Test to make sure that when we attempt to enable a plugin, it is
+        actually enabled in the JSON for the build_request after running
+        build_request.render()
+        """
+
+        plugin_type = "exit_plugins"
+        plugin_name = "testing_exit_plugin"
+        plugin_args = {"foo": "bar"}
+
+        build_request = BuildRequest(INPUTS_PATH)
+        build_request.customize_conf['enable_plugins'].append(
+            {
+                "plugin_type": plugin_type,
+                "plugin_name": plugin_name,
+                "plugin_args": plugin_args
+            }
+        )
+        kwargs = get_sample_prod_params()
+        build_request.set_params(**kwargs)
+        build_request.render()
+
+        assert {
+                "name": plugin_name,
+                "args": plugin_args
+        } in build_request.dj.dock_json[plugin_type]
+
+    def test_render_prod_custom_site_plugin_disable(self):
+        """
+        Test to make sure that when we attempt to disable a plugin, it is
+        actually disabled in the JSON for the build_request after running
+        build_request.render()
+        """
+
+        plugin_type = "postbuild_plugins"
+        plugin_name = "compress"
+
+        build_request = BuildRequest(INPUTS_PATH)
+        build_request.customize_conf['disable_plugins'].append(
+            {
+                "plugin_type": plugin_type,
+                "plugin_name": plugin_name
+            }
+        )
+        kwargs = get_sample_prod_params()
+        build_request.set_params(**kwargs)
+        build_request.render()
+
+        for plugin in build_request.dj.dock_json[plugin_type]:
+            if plugin['name'] == plugin_name:
+                assert False
+
+    def test_render_prod_custom_site_plugin_override(self):
+        """
+        Test to make sure that when we attempt to override a plugin's args,
+        they are actually overridden in the JSON for the build_request
+        after running build_request.render()
+        """
+
+        plugin_type = "postbuild_plugins"
+        plugin_name = "compress"
+        plugin_args = {"foo": "bar"}
+
+        kwargs = get_sample_prod_params()
+
+        unmodified_build_request = BuildRequest(INPUTS_PATH)
+        unmodified_build_request.set_params(**kwargs)
+        unmodified_build_request.render()
+
+        for plugin_dict in unmodified_build_request.dj.dock_json[plugin_type]:
+            if plugin_dict['name'] == plugin_name:
+                plugin_index = unmodified_build_request.dj.dock_json[plugin_type].index(plugin_dict)
+
+        build_request = BuildRequest(INPUTS_PATH)
+        build_request.customize_conf['enable_plugins'].append(
+            {
+                "plugin_type": plugin_type,
+                "plugin_name": plugin_name,
+                "plugin_args": plugin_args
+            }
+        )
+        build_request.set_params(**kwargs)
+        build_request.render()
+
+
+        assert {
+                "name": plugin_name,
+                "args": plugin_args
+        } in build_request.dj.dock_json[plugin_type]
+
+        assert unmodified_build_request.dj.dock_json[plugin_type][plugin_index]['name'] == plugin_name
+        assert build_request.dj.dock_json[plugin_type][plugin_index]['name'] == plugin_name