You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by pi...@apache.org on 2023/01/11 23:14:37 UTC

[airflow] branch v2-5-test updated: Cleanup and do housekeeping with plugin examples (#28537)

This is an automated email from the ASF dual-hosted git repository.

pierrejeambrun pushed a commit to branch v2-5-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v2-5-test by this push:
     new 92c0b34571 Cleanup and do housekeeping with plugin examples (#28537)
92c0b34571 is described below

commit 92c0b345712e38234639734eafbbf7337482b962
Author: Jarek Potiuk <ja...@potiuk.com>
AuthorDate: Thu Dec 22 23:03:03 2022 +0100

    Cleanup and do housekeeping with plugin examples (#28537)
    
    This PR performs housekeeping of the plugin examples:
    
    * makes the examples independent of Hive being installed
    * adds "has_access" in the examples
    * removes the misleading "metastore" (which is hive metastore not
      Airflow Metastore as used in other places
    
    This way our example will be much easier to apply by anyone.
    
    (cherry picked from commit 66eb282b7d8746bcb5d90117479c35ae5ca0cfbc)
---
 .dockerignore                                      |   1 -
 .../airflow_breeze/utils/docker_command_utils.py   |   1 -
 dev/breeze/tests/test_commands.py                  |   8 +-
 .../apache-airflow/empty_plugin}/README.md         |   4 +-
 docs/apache-airflow/empty_plugin/empty_plugin.py   |  60 +++++++
 .../empty_plugin/templates/empty_plugin/index.html |  24 ++-
 docs/apache-airflow/howto/custom-view-plugin.rst   |  72 ++------
 docs/apache-airflow/plugins.rst                    |  12 ++
 metastore_browser/hive_metastore.py                | 199 ---------------------
 .../templates/metastore_browser/base.html          |  57 ------
 .../templates/metastore_browser/db.html            |  46 -----
 .../templates/metastore_browser/table.html         | 152 ----------------
 scripts/ci/docker-compose/local.yml                |   3 -
 13 files changed, 109 insertions(+), 530 deletions(-)

diff --git a/.dockerignore b/.dockerignore
index 7a33552d0e..045b730630 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -34,7 +34,6 @@
 !chart
 !docs
 !licenses
-!metastore_browser
 
 # Add those folders to the context so that they are available in the CI container
 !scripts/in_container
diff --git a/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py b/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py
index b9e30dd21b..750cf76833 100644
--- a/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py
+++ b/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py
@@ -104,7 +104,6 @@ VOLUMES_FOR_SELECTED_MOUNTS = [
     ("kubernetes_tests", "/opt/airflow/kubernetes_tests"),
     ("docker_tests", "/opt/airflow/docker_tests"),
     ("chart", "/opt/airflow/chart"),
-    ("metastore_browser", "/opt/airflow/metastore_browser"),
 ]
 
 
diff --git a/dev/breeze/tests/test_commands.py b/dev/breeze/tests/test_commands.py
index 8b53228bae..bbff4d7089 100644
--- a/dev/breeze/tests/test_commands.py
+++ b/dev/breeze/tests/test_commands.py
@@ -27,23 +27,23 @@ def test_visuals():
 
 def test_get_extra_docker_flags_all():
     flags = get_extra_docker_flags(MOUNT_ALL)
-    assert "empty" not in "".join(flags)
+    assert "/empty," not in "".join(flags)
     assert len(flags) < 10
 
 
 def test_get_extra_docker_flags_selected():
     flags = get_extra_docker_flags(MOUNT_SELECTED)
-    assert "empty" not in "".join(flags)
+    assert "/empty," not in "".join(flags)
     assert len(flags) > 40
 
 
 def test_get_extra_docker_flags_remove():
     flags = get_extra_docker_flags(MOUNT_REMOVE)
-    assert "empty" in "".join(flags)
+    assert "/empty," in "".join(flags)
     assert len(flags) < 10
 
 
 def test_get_extra_docker_flags_skip():
     flags = get_extra_docker_flags(MOUNT_SKIP)
-    assert "empty" not in "".join(flags)
+    assert "/empty," not in "".join(flags)
     assert len(flags) < 10
diff --git a/metastore_browser/README.md b/docs/apache-airflow/empty_plugin/README.md
similarity index 90%
rename from metastore_browser/README.md
rename to docs/apache-airflow/empty_plugin/README.md
index ddd414a2d3..574c20ab10 100644
--- a/metastore_browser/README.md
+++ b/docs/apache-airflow/empty_plugin/README.md
@@ -17,11 +17,11 @@
  under the License.
 -->
 
-# Apache Hive metastore plugin
+# Apache example plugin
 
 This is an example plugin for Apache Airflow.
 
-This plugin allows you to view Apache Hive metastore from the web UI interface.
+This plugin displays empty view.
 
 ## Installation
 
diff --git a/docs/apache-airflow/empty_plugin/empty_plugin.py b/docs/apache-airflow/empty_plugin/empty_plugin.py
new file mode 100644
index 0000000000..abf7b6d802
--- /dev/null
+++ b/docs/apache-airflow/empty_plugin/empty_plugin.py
@@ -0,0 +1,60 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Plugins example"""
+from __future__ import annotations
+
+from flask import Blueprint
+from flask_appbuilder import BaseView, expose
+
+from airflow.plugins_manager import AirflowPlugin
+from airflow.security import permissions
+from airflow.www.auth import has_access
+
+
+class EmptyPluginView(BaseView):
+    """Creating a Flask-AppBuilder View"""
+
+    default_view = "index"
+
+    @expose("/")
+    @has_access(
+        [
+            (permissions.ACTION_CAN_READ, permissions.RESOURCE_WEBSITE),
+        ]
+    )
+    def index(self):
+        """Create default view"""
+        return self.render_template("empty_plugin/index.html", name="Empty Plugin")
+
+
+# Creating a flask blueprint
+bp = Blueprint(
+    "Empty Plugin",
+    __name__,
+    template_folder="templates",
+    static_folder="static",
+    static_url_path="/static/empty_plugin",
+)
+
+
+class EmptyPlugin(AirflowPlugin):
+    """Defining the plugin class"""
+
+    name = "Empty Plugin"
+    flask_blueprints = [bp]
+    appbuilder_views = [{"name": "Empty Plugin", "category": "Extra Views", "view": EmptyPluginView()}]
diff --git a/metastore_browser/templates/metastore_browser/dbs.html b/docs/apache-airflow/empty_plugin/templates/empty_plugin/index.html
similarity index 64%
rename from metastore_browser/templates/metastore_browser/dbs.html
rename to docs/apache-airflow/empty_plugin/templates/empty_plugin/index.html
index 6a6e187a1a..39489b172c 100644
--- a/metastore_browser/templates/metastore_browser/dbs.html
+++ b/docs/apache-airflow/empty_plugin/templates/empty_plugin/index.html
@@ -17,11 +17,23 @@
  under the License.
 #}
 
-{% extends 'metastore_browser/base.html' %}
+{% extends base_template %}
 
-{% block plugin_content %}
-    <h4>
-        <span>Hive Databases</span>
-    </h4>
-    {{ table }}
+{% block head %}
+  {{ super() }}
+{% endblock %}
+
+{% block body %}
+  <div>
+    <h3 style="float: left">
+        {% block page_header %}{{ name }}{% endblock%}
+    </h3>
+    <div id="object" class="select2-drop-mask" style="margin-top: 25px; width: 400px;float: right"></div>
+    <div style="clear: both"></div>
+  </div>
+  {% block plugin_content %}{% endblock %}
+{% endblock %}
+
+{% block tail %}
+  {{ super() }}
 {% endblock %}
diff --git a/docs/apache-airflow/howto/custom-view-plugin.rst b/docs/apache-airflow/howto/custom-view-plugin.rst
index e6ccad4562..914890073b 100644
--- a/docs/apache-airflow/howto/custom-view-plugin.rst
+++ b/docs/apache-airflow/howto/custom-view-plugin.rst
@@ -16,27 +16,22 @@
     under the License.
 
 
-Customize view of Apache Hive Metastore from Airflow web UI
-===========================================================
+Customize view of Apache from Airflow web UI
+============================================
 
 Airflow has feature that allows to integrate a custom UI along with its
 core UI using the Plugin manager
 
-This is an example plugin for Airflow that allows to create custom view of
-Apache Hive metastore from the web UI of Airflow. Showing Metastore information
-like list of the tables in database, finding the table object/ ddl information
-for given table, retrieving partition information, retrieving data from the table,
-retrieving table objects from Hive Metastore are some of the custom views shown
-in this example.
+This is an example plugin for Airflow that displays absolutely nothing.
 
 In this plugin, two object reference are derived from the base class
 ``airflow.plugins_manager.AirflowPlugin``. They are flask_blueprints and
 appbuilder_views
 
 Using flask_blueprints in Airflow plugin, the core application can be extended
-to support the application that is customized to view Apache Hive Metastore.
+to support the application that is customized to view Empty Plugin.
 In this object reference, the list of Blueprint object with the static template for
-rendering the Metastore information is passed on.
+rendering the information.
 
 Using appbuilder_views in Airflow plugin, a class that represents a concept is
 added and presented with views and methods to implement it.
@@ -48,43 +43,16 @@ Custom view Registration
 ------------------------
 
 A custom view with object reference to flask_appbuilder and Blueprint from flask
-and be registered as a part of a :doc:`plugin </plugins>`. The following is a
-skeleton for us to implement a new custom view:
+and be registered as a part of a :doc:`plugin </authoring-and-scheduling/plugins>`.
 
-.. code-block:: python
-
-    from airflow.plugins_manager import AirflowPlugin
-    from flask import Blueprint
-    from flask_appbuilder import BaseView
-
-
-    class MetastoreBrowserView(BaseView):
-        pass
-
-
-    # Creating a flask blueprint to integrate the templates and static folder
-    bp = Blueprint(
-        "metastore_browser",
-        __name__,
-        template_folder="templates",
-        static_folder="static",
-        static_url_path="/static/metastore_browser",
-    )
+The following is a skeleton for us to implement a new custom view:
 
+.. exampleinclude:: ../empty_plugin/empty_plugin.py
+    :language: python
 
-    class MetastoreBrowserPlugin(AirflowPlugin):
-        name = "metastore_browser"
-        flask_blueprints = [bp]
-        appbuilder_views = [
-            {
-                "category": "Plugins",  # name of the tab in Airflow UI
-                "name": "Hive Metadata Browser",  # name of link under the tab
-                "view": MetastoreBrowserView(),
-            }
-        ]
 
 ``Plugins`` specified in the ``category`` key of ``appbuilder_views`` dictionary is
-the name of the tab in the navigation bar of the Airflow UI. ``Hive Metastore Browser``
+the name of the tab in the navigation bar of the Airflow UI. ``Empty Plugin``
 is the name of the link under the tab ``Plugins``, which will launch the plugin
 
 We have to add Blueprint for generating the part of the application
@@ -92,29 +60,15 @@ that needs to be rendered in Airflow web UI. We can define templates, static fil
 and this blueprint will be registered as part of the Airflow application when the
 plugin gets loaded.
 
-Next, we can add code into ``MetastoreBrowserView`` with views and implementing
-methods for each of those views. After the implementation, the custom view
-created becomes part of the Airflow web UI.
-
-For reference, here's the plugin code within ``MetastoreBrowserView`` class that shows list of tables in the database:
-
-.. exampleinclude:: ../../../metastore_browser/hive_metastore.py
-    :language: python
-    :start-after: [START howto_customview_show_database_table]
-    :end-before: [END howto_customview_show_database_table]
-
 The ``$AIRFLOW_HOME/plugins`` folder with custom view UI have the following folder structure.
 
 ::
 
     plugins
-    ├── hive_metastore.py
+    ├── empty_plugin.py
     ├── templates
-    |   └── metastore_browser
-    |       ├── base.html
-    |       ├── db.html
-    |       ├── dbs.html
-    │       └── table.html
+    |   └── empty_plugin
+    |       ├── index.html
     └── README.md
 
 The HTML files required to render the views built is added as part of the
diff --git a/docs/apache-airflow/plugins.rst b/docs/apache-airflow/plugins.rst
index ae011caa12..47c65357ec 100644
--- a/docs/apache-airflow/plugins.rst
+++ b/docs/apache-airflow/plugins.rst
@@ -168,6 +168,8 @@ definitions in Airflow.
 
     # This is the class you derive to create a plugin
     from airflow.plugins_manager import AirflowPlugin
+    from airflow.security import permissions
+    from airflow.www.auth import has_access
 
     from flask import Blueprint
     from flask_appbuilder import expose, BaseView as AppBuilderBaseView
@@ -201,6 +203,11 @@ definitions in Airflow.
         default_view = "test"
 
         @expose("/")
+        @has_access(
+            [
+                (permissions.ACTION_CAN_READ, permissions.RESOURCE_WEBSITE),
+            ]
+        )
         def test(self):
             return self.render_template("test_plugin/test.html", content="Hello galaxy!")
 
@@ -210,6 +217,11 @@ definitions in Airflow.
         default_view = "test"
 
         @expose("/")
+        @has_access(
+            [
+                (permissions.ACTION_CAN_READ, permissions.RESOURCE_WEBSITE),
+            ]
+        )
         def test(self):
             return self.render_template("test_plugin/test.html", content="Hello galaxy!")
 
diff --git a/metastore_browser/hive_metastore.py b/metastore_browser/hive_metastore.py
deleted file mode 100644
index 6848935797..0000000000
--- a/metastore_browser/hive_metastore.py
+++ /dev/null
@@ -1,199 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-"""Plugins metabrowser"""
-from __future__ import annotations
-
-import json
-from datetime import datetime
-
-import pandas as pd
-from flask import Blueprint, request
-from flask_appbuilder import BaseView, expose
-from markupsafe import Markup
-
-from airflow.plugins_manager import AirflowPlugin
-from airflow.providers.apache.hive.hooks.hive import HiveCliHook, HiveMetastoreHook
-from airflow.providers.mysql.hooks.mysql import MySqlHook
-from airflow.providers.presto.hooks.presto import PrestoHook
-from airflow.www.decorators import gzipped
-
-METASTORE_CONN_ID = "metastore_default"
-METASTORE_MYSQL_CONN_ID = "metastore_mysql"
-PRESTO_CONN_ID = "presto_default"
-HIVE_CLI_CONN_ID = "hive_default"
-DEFAULT_DB = "default"
-DB_ALLOW_LIST: list[str] = []
-DB_DENY_LIST: list[str] = ["tmp"]
-TABLE_SELECTOR_LIMIT = 2000
-
-# Keeping pandas from truncating long strings
-pd.set_option("display.max_colwidth", -1)
-
-
-class MetastoreBrowserView(BaseView):
-    """Creating a Flask-AppBuilder BaseView"""
-
-    default_view = "index"
-
-    @expose("/")
-    def index(self):
-        """Create default view"""
-        sql = """
-        SELECT
-            a.name as db, db_location_uri as location,
-            count(1) as object_count, a.desc as description
-        FROM DBS a
-        JOIN TBLS b ON a.DB_ID = b.DB_ID
-        GROUP BY a.name, db_location_uri, a.desc
-        """
-        hook = MySqlHook(METASTORE_MYSQL_CONN_ID)
-        df = hook.get_pandas_df(sql)
-        df.db = '<a href="/metastorebrowserview/db/?db=' + df.db + '">' + df.db + "</a>"
-        table = df.to_html(
-            classes="table table-striped table-bordered table-hover",
-            index=False,
-            escape=False,
-            na_rep="",
-        )
-        return self.render_template("metastore_browser/dbs.html", table=Markup(table))
-
-    # [START howto_customview_table]
-    @expose("/table/")
-    def table(self):
-        """Create table view"""
-        table_name = request.args.get("table")
-        metastore = HiveMetastoreHook(METASTORE_CONN_ID)
-        table = metastore.get_table(table_name)
-        return self.render_template(
-            "metastore_browser/table.html", table=table, table_name=table_name, datetime=datetime, int=int
-        )
-
-    # [END howto_customview_table]
-
-    # [START howto_customview_show_database_table]
-    @expose("/db/")
-    def db(self):
-        """Show tables in database"""
-        db = request.args.get("db")
-        metastore = HiveMetastoreHook(METASTORE_CONN_ID)
-        tables = sorted(metastore.get_tables(db=db), key=lambda x: x.tableName)
-        return self.render_template("metastore_browser/db.html", tables=tables, db=db)
-
-    # [END howto_customview_show_database_table]
-
-    # [START howto_customview_partitions_info]
-    @gzipped
-    @expose("/partitions/")
-    def partitions(self):
-        """Retrieve table partitions"""
-        schema, table = request.args.get("table").split(".")
-        sql = f"""
-        SELECT
-            a.PART_NAME,
-            a.CREATE_TIME,
-            c.LOCATION,
-            c.IS_COMPRESSED,
-            c.INPUT_FORMAT,
-            c.OUTPUT_FORMAT
-        FROM PARTITIONS a
-        JOIN TBLS b ON a.TBL_ID = b.TBL_ID
-        JOIN DBS d ON b.DB_ID = d.DB_ID
-        JOIN SDS c ON a.SD_ID = c.SD_ID
-        WHERE
-            b.TBL_NAME like '{table}' AND
-            d.NAME like '{schema}'
-        ORDER BY PART_NAME DESC
-        """
-        hook = MySqlHook(METASTORE_MYSQL_CONN_ID)
-        df = hook.get_pandas_df(sql)
-        return df.to_html(
-            classes="table table-striped table-bordered table-hover",
-            index=False,
-            na_rep="",
-        )
-
-    # [END howto_customview_partitions_info]
-
-    @gzipped
-    @expose("/objects/")
-    def objects(self):
-        """Retrieve objects from TBLS and DBS"""
-        where_clause = ""
-        if DB_ALLOW_LIST:
-            dbs = ",".join("'" + db + "'" for db in DB_ALLOW_LIST)
-            where_clause = f"AND b.name IN ({dbs})"
-        if DB_DENY_LIST:
-            dbs = ",".join("'" + db + "'" for db in DB_DENY_LIST)
-            where_clause = f"AND b.name NOT IN ({dbs})"
-        sql = f"""
-        SELECT CONCAT(b.NAME, '.', a.TBL_NAME), TBL_TYPE
-        FROM TBLS a
-        JOIN DBS b ON a.DB_ID = b.DB_ID
-        WHERE
-            a.TBL_NAME NOT LIKE '%tmp%' AND
-            a.TBL_NAME NOT LIKE '%temp%' AND
-            b.NAME NOT LIKE '%tmp%' AND
-            b.NAME NOT LIKE '%temp%'
-        {where_clause}
-        LIMIT {TABLE_SELECTOR_LIMIT};
-        """
-        hook = MySqlHook(METASTORE_MYSQL_CONN_ID)
-        data = [{"id": row[0], "text": row[0]} for row in hook.get_records(sql)]
-        return json.dumps(data)
-
-    @gzipped
-    @expose("/data/")
-    def data(self):
-        """Retrieve data from table"""
-        table = request.args.get("table")
-        sql = f"SELECT * FROM {table} LIMIT 1000;"
-        hook = PrestoHook(PRESTO_CONN_ID)
-        df = hook.get_pandas_df(sql)
-        return df.to_html(
-            classes="table table-striped table-bordered table-hover",
-            index=False,
-            na_rep="",
-        )
-
-    @expose("/ddl/")
-    def ddl(self):
-        """Retrieve table ddl"""
-        table = request.args.get("table")
-        sql = f"SHOW CREATE TABLE {table};"
-        hook = HiveCliHook(HIVE_CLI_CONN_ID)
-        return hook.run_cli(sql)
-
-
-# Creating a flask blueprint to integrate the templates and static folder
-bp = Blueprint(
-    "metastore_browser",
-    __name__,
-    template_folder="templates",
-    static_folder="static",
-    static_url_path="/static/metastore_browser",
-)
-
-
-class MetastoreBrowserPlugin(AirflowPlugin):
-    """Defining the plugin class"""
-
-    name = "metastore_browser"
-    flask_blueprints = [bp]
-    appbuilder_views = [
-        {"name": "Hive Metadata Browser", "category": "Plugins", "view": MetastoreBrowserView()}
-    ]
diff --git a/metastore_browser/templates/metastore_browser/base.html b/metastore_browser/templates/metastore_browser/base.html
deleted file mode 100644
index 94ba0e1010..0000000000
--- a/metastore_browser/templates/metastore_browser/base.html
+++ /dev/null
@@ -1,57 +0,0 @@
-{#
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements.  See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership.  The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License.  You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied.  See the License for the
- specific language governing permissions and limitations
- under the License.
-#}
-
-{% extends base_template %}
-
-{% block head %}
-  {{ super() }}
-  <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='dataTables.bootstrap.css') }}">
-  <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='select2.css') }}">
-{% endblock %}
-
-{% block body %}
-  <div>
-    <h3 style="float: left">
-        {% block page_header %}Hive Metastore Browser{% endblock%}
-    </h3>
-    <div id="object" class="select2-drop-mask" style="margin-top: 25px; width: 400px;float: right"></div>
-    <div style="clear: both"></div>
-  </div>
-  {% block plugin_content %}{% endblock %}
-{% endblock %}
-
-{% block tail %}
-  {{ super() }}
-  <script src="{{ url_for('static', filename='jquery.dataTables.min.js') }}"></script>
-  <script src="{{ url_for('static', filename='dataTables.bootstrap.js') }}"></script>
-  <script src="{{ url_for('static', filename='select2.min.js') }}"></script>
-  <script>
-    // Filling up the table selector
-    url = "{{ url_for('.objects') }}";
-    $.get(url, function( data ) {
-      $("#object").select2({
-        data: data,
-        placeholder: "Table Selector",
-      })
-      .on("change", function(e){
-        window.location = "{{ url_for('.table') }}?table=" + e.val;
-      });
-    }, "json");
-  </script>
-{% endblock %}
diff --git a/metastore_browser/templates/metastore_browser/db.html b/metastore_browser/templates/metastore_browser/db.html
deleted file mode 100644
index 5f3b9565f4..0000000000
--- a/metastore_browser/templates/metastore_browser/db.html
+++ /dev/null
@@ -1,46 +0,0 @@
-{#
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements.  See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership.  The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License.  You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied.  See the License for the
- specific language governing permissions and limitations
- under the License.
-#}
-
-{% extends 'metastore_browser/base.html' %}
-
-{% block plugin_content %}
-  <h3 style="float: left">
-    <span class="text-muted">Database:</span> {{ db }}
-  </h3>
-  <table class="table table-striped table-bordered table-hover">
-    <thead>
-      <tr>
-        <th>Table</th>
-        <th>Owner</th>
-      </tr>
-    </thead>
-    <tbody>
-      {%for table in tables %}
-        <tr>
-          <td>
-            <a href="{{ url_for('.table', table=table.dbName + '.' + table.tableName) }}">
-              {{ table.tableName }}
-            </a>
-          </td>
-          <td>{{ table.owner }}</td>
-        </tr>
-      {% endfor %}
-    </tbody>
-  </table>
-{% endblock %}
diff --git a/metastore_browser/templates/metastore_browser/table.html b/metastore_browser/templates/metastore_browser/table.html
deleted file mode 100644
index c62615cf4d..0000000000
--- a/metastore_browser/templates/metastore_browser/table.html
+++ /dev/null
@@ -1,152 +0,0 @@
-{#
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements.  See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership.  The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License.  You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied.  See the License for the
- specific language governing permissions and limitations
- under the License.
-#}
-
-{% extends 'metastore_browser/base.html' %}
-
-{% block plugin_content %}
-<div>
-  <h4>
-    <span class="text-muted">Table:</span> {{ table.dbName }}.{{ table.tableName }}
-  </h4>
-</div>
-<ul class="nav nav-tabs" id="tabs" style="clear: both">
-    <li role="presentation" class="active"><a href="#home" aria-controls="fields" role="tab" data-toggle="tab">Fields</a></li>
-    <li role="presentation"><a href="#data" aria-controls="data" role="tab" data-toggle="tab">Sample Data</a></li>
-    <li role="presentation"><a href="#partitions" aria-controls="partitions" role="tab" data-toggle="tab">Partitions</a></li>
-    <li role="presentation"><a href="#attributes" aria-controls="attributes" role="tab" data-toggle="tab">Attributes</a></li>
-    <li role="presentation"><a href="#parameters" aria-controls="parameters" role="tab" data-toggle="tab">Parameters</a></li>
-    <li role="presentation"><a href="#ddl" aria-controls="ddl" role="tab" data-toggle="tab">DDL</a></li>
-</ul>
-<div class="tab-content">
-  <div role="tabpanel" class="tab-pane fade in active" id="home">
-    <table class="table table-striped table-bordered table-hover">
-        <thead>
-            <tr>
-                <th>Column</th>
-                <th>Type</th>
-                <th>Comment</th>
-            </tr>
-        </thead>
-        <tbody>
-        {%for col in table.partitionKeys %}
-            <tr>
-                <td><b>{{ col.name }}</b> (PARTITION)</td>
-                <td>{{ col.type }}</td>
-                <td>{{ col.comment or '' }}</td>
-            </tr>
-        {% endfor %}
-        {%for col in table.sd.cols %}
-            <tr>
-                <td>{{ col.name }}</td>
-                <td>{{ col.type }}</td>
-                <td>{{ col.comment or '' }}</td>
-            </tr>
-        {% endfor %}
-        </tbody>
-    </table>
-  </div>
-  <div role="tabpanel" class="tab-pane fade" id="data" style="white-space:nowrap; margin-top: 10px;">
-      <img src="{{ url_for('static', filename='loading.gif') }}" width="50" style="margin: 20px">
-  </div>
-  <div role="tabpanel" class="tab-pane fade" id="partitions" style="white-space:nowrap; margin-top: 10px;">
-      <img src="{{ url_for('static', filename='loading.gif') }}" width="50" style="margin: 20px">
-  </div>
-  <div role="tabpanel" class="tab-pane fade" id="attributes">
-    <table class="table table-striped table-bordered table-hover">
-        <thead>
-            <tr>
-                <th>Attribute</th>
-                <th>Value</th>
-            </tr>
-        </thead>
-        <tbody>
-        {%for k, v in table.__dict__.items() %}
-            {% if v and k not in ('sd', 'partitionKeys', 'tableName', 'parameters') %}
-            <tr>
-                <td>{{ k }}</td>
-                {% if k.endswith('Time') %}
-                    <td>{{ datetime.fromtimestamp(int(v)).isoformat() }}</td>
-                {% else %}
-                    <td>{{ v }}</td>
-                {% endif %}
-            </tr>
-            {% endif %}
-        {% endfor %}
-        </tbody>
-    </table>
-  </div>
-  <div role="tabpanel" class="tab-pane fade" id="parameters">
-    <table class="table table-striped table-bordered table-hover">
-        <thead>
-            <tr>
-                <th>Parameter</th>
-                <th>Value</th>
-            </tr>
-        </thead>
-        <tbody>
-        {%for k, v in table.parameters.items() %}
-            {% if v and k not in [] %}
-            <tr>
-                <td>{{ k }}</td>
-                {% if k.endswith('Time') %}
-                    <td>{{ datetime.fromtimestamp(int(v)).isoformat() }}</td>
-                {% else %}
-                    <td>{{ v }}</td>
-                {% endif %}
-            </tr>
-            {% endif %}
-        {% endfor %}
-        </tbody>
-    </table>
-  </div>
-  <div role="tabpanel" class="tab-pane fade" id="ddl">
-      <pre id="ddl_content">
-        <img src="{{ url_for('static', filename='loading.gif') }}" width="50" style="margin: 20px">
-      </pre>
-  </div>
-</div>
-{% endblock %}
-
-{% block tail %}
-{{ super() }}
-<script>
-$('#tabs a').click(function (e) {
-    //e.preventDefault();
-    $(this).tab('show');
-});
-
-$.get("{{ url_for(".data" , table=table_name) }}", function( data ) {
-    $("#data").html(data);
-    $('#data table.dataframe').dataTable({
-        "iDisplayLength": 30,
-    });
-});
-
-$.get("{{ url_for(".partitions" , table=table_name) }}", function( data ) {
-    $("#partitions").html(data);
-    $('#partitions table.dataframe').dataTable({
-        "iDisplayLength": 30,
-    });
-});
-
-$.get("{{ url_for(".ddl" , table=table_name) }}", function( data ) {
-    $("#ddl_content").html(data);
-});
-</script>
-{% endblock %}
diff --git a/scripts/ci/docker-compose/local.yml b/scripts/ci/docker-compose/local.yml
index b56b44af0a..0b0c8461a7 100644
--- a/scripts/ci/docker-compose/local.yml
+++ b/scripts/ci/docker-compose/local.yml
@@ -123,7 +123,4 @@ services:
       - type: bind
         source: ../../../chart
         target: /opt/airflow/chart
-      - type: bind
-        source: ../../../metastore_browser
-        target: /opt/airflow/metastore_browser
         # END automatically generated volumes from VOLUMES_FOR_SELECTED_MOUNTS in docker_command_utils.py