Blame osbs-client-site-customizations.patch

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