summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbenoitc <bchesneau@gmail.com>2014-02-13 16:38:39 +0100
committerbenoitc <bchesneau@gmail.com>2014-02-13 16:38:39 +0100
commit20a68fec4d762ccb20862edf6d6b85fbd25344ee (patch)
tree1551efe6e8c3a1baf31246ba4f6f772ac734c7af
parentae6eae31a1f08b7116c3b47257fd34a01287427f (diff)
downloadcouchdb-20a68fec4d762ccb20862edf6d6b85fbd25344ee.tar.gz
remove couch_plugins
-rw-r--r--apps/couch_plugins/README.md159
-rw-r--r--apps/couch_plugins/src/couch_plugins.app.src23
-rw-r--r--apps/couch_plugins/src/couch_plugins.erl300
-rw-r--r--apps/couch_plugins/src/couch_plugins_httpd.erl65
4 files changed, 0 insertions, 547 deletions
diff --git a/apps/couch_plugins/README.md b/apps/couch_plugins/README.md
deleted file mode 100644
index b00a080c1..000000000
--- a/apps/couch_plugins/README.md
+++ /dev/null
@@ -1,159 +0,0 @@
-Heya,
-
-I couldn’t help myself thinking about plugin stuff and ended up
-whipping up a proof of concept.
-
-Here’s a <1 minute demo video:
-
- https://dl.dropboxusercontent.com/u/82149/couchdb-plugins-demo.mov
-
-Alternative encoding:
-
- https://dl.dropboxusercontent.com/u/82149/couchdb-plugins-demo.m4v)
-
-
-In my head the whole plugin idea is a very wide area, but I was so
-intrigued by the idea of getting something running with a click on a
-button in Futon. So I looked for a minimally viable plugin system.
-
-
-## Design principles
-
-It took me a day to put this all together and this was only possible
-because I took a lot of shortcuts. I believe they are all viable for a
-first iteration of a plugins system:
-
-1. Install with one click on a button in Futon (or HTTP call)
-2. Only pure Erlang plugins are allowed.
-3. The plugin author must provide a binary package for each Erlang (and,
- later, each CouchDB version).
-4. Complete trust-based system. You trust me to not do any nasty things
- when you click on the install button. No crypto, no nothing. Only
- people who can commit to Futon can release new versions of plugins.
-5. Minimal user-friendlyness: won’t install plugins that don’t match
- the current Erlang version, gives semi-sensible error messages
- (wrapped in a HTTP 500 response :)
-6. Require a pretty strict format for binary releases.
-
-
-## Roadmap
-
-Here’s a list of things this first iterations does and doesn’t do:
-
-- Pure Erlang plugins only. No C-dependencies, no JavaScript, no nothing.
-- No C-dependencies.
-- Install a plugin via Futon (or HTTP call). Admin only.
-- A hardcoded list of plugins in Futon.
-- Loads a pre-packaged, pre-compiled .tar.gz file from a URL.
-- Only installs if Erlang version matches.
-- No security checking of binaries.
-- No identity checking of binaries.
-- Register installed plugins in the config system.
-- Make sure plugins start with the next restart of CouchDB.
-- Uninstall a plugin via Futon (or HTTP call). Admin only.
-- Show when a particular plugin is installed.
-- Only installs if CouchDB version matches.
-- Serve static web assets (for Futon/Fauxton) from `/_plugins/<name>/`.
-
-I hope you agree we can ship this with a few warnings so people can get a
-hang of it.
-
-
-A roadmap, progress and issues can be found here:
-
-https://issues.apache.org/jira/issues/?jql=component+%3D+Plugins+AND+project+%3D+COUCHDB+AND+resolution+%3D+Unresolved+ORDER+BY+priority+DESC
-
-
-
-## How it works
-
-This plugin system lives in `src/couch_plugins` and is a tiny CouchDB
-module.
-
-It exposes one new API endpoint `/_plugins` that an admin user can
-POST to.
-
-The additional Futon page lives at `/_utils/plugins.html` it is
-hardcoded.
-
-Futon (or you) post an object to `/_plugins` with four properties:
-
- {
- "name": "geocouch", // name of the plugin, must be unique
- "url": "http://people.apache.org/~jan", // “base URL” for plugin releases (see below)
- "version": "couchdb1.2.x_v0.3.0-11-g4ea0bea", // whatever version internal to the plugin
- "checksums": {
- "R15B03": "ZetgdHj2bY2w37buulWVf3USOZs=" // base64’d sha hash over the binary
- }
- }
-
-`couch_plugins` then attempts to download a .tar.gz from this
-location:
-
- http://people.apache.org/~jan/geocouch-couchdb1.2.x_v0.3.0-12-g4ea0bea-R15B03.tar.gz
-
-It should be obvious how the URL is constructed from the POST data.
-(This url is live, feel free to play around with this tarball).
-
-Next it calculates the sha hash for the downloaded .tar.gz file and
-matches it against the correct version in the `checksums` parameter.
-
-If that succeeds, we unpack the .tar.gz file (currently in `/tmp`,
-need to find a better place for this) and adds the extracted directory
-to the Erlang code path
-(`code:add_path("/tmp/couchdb_plugins/geocouch-couchdb1.2.x_v0.3.0-12-g4ea0bea-R15B03/ebin")`)
-and loads the included application (`application:load(geocouch)`).
-
-Then it looks into the `./priv/default.d` directory that lives next to
-`ebin/` in the plugin directory for configuration `.ini` files and loads them.
-On next startup these configuration files are loaded after global defaults,
-and before any local configuration.
-
-If that all goes to plan, we report success back to the HTTP caller.
-
-That’s it! :)
-
-It’s deceptively simple, probably does a few things very wrong and
-leaves a few things open (see above).
-
-One open question I’d like an answer for is finding a good location to
-unpack & install the plugin files that isn’t `tmp`. If the answer is
-different for a pre-BigCouch/rcouch-merge and post-BigCouch/rcouch-
-merge world, I’d love to know :)
-
-
-## Code
-
-The main branch for this is 1867-feature-plugins:
-
- ASF: https://git-wip-us.apache.org/repos/asf?p=couchdb.git;a=log;h=refs/heads/1867-feature-plugins
- GitHub: https://github.com/janl/couchdb/compare/apache:master...1867-feature-plugins
-
-I created a branch on GeoCouch that adds a few lines to its `Makefile`
-that shows how a binary package is built:
-
- https://github.com/janl/geocouch/compare/couchbase:couchdb1.3.x...couchdb1.3.x-plugins
-
-
-## Build
-
-Build CouchDB as usual:
-
- ./bootstrap
- ./configure
- make
- make dev
- ./utils/run
-
-* * *
-
-I hope you like this :) Please comment and improve heavily!
-
-Let me know if you have any questions :)
-
-If you have any criticism, please phrase it in a way that we can use
-to improve this, thanks!
-
-Best,
-Jan
---
diff --git a/apps/couch_plugins/src/couch_plugins.app.src b/apps/couch_plugins/src/couch_plugins.app.src
deleted file mode 100644
index 9e69b84c2..000000000
--- a/apps/couch_plugins/src/couch_plugins.app.src
+++ /dev/null
@@ -1,23 +0,0 @@
-% Licensed 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.
-{application, couch_plugins,
- [
- {description, "A CouchDB Plugin Installer"},
- {vsn, "0.1.0"},
- {registered, []},
- {applications, [
- kernel,
- stdlib
- ]},
- {mod, { couch_plugins_app, []}},
- {env, []}
- ]}.
diff --git a/apps/couch_plugins/src/couch_plugins.erl b/apps/couch_plugins/src/couch_plugins.erl
deleted file mode 100644
index dcbd2d373..000000000
--- a/apps/couch_plugins/src/couch_plugins.erl
+++ /dev/null
@@ -1,300 +0,0 @@
-% Licensed 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.
--module(couch_plugins).
--include_lib("couch/include/couch_db.hrl").
--export([install/1, uninstall/1]).
-
-% couch_plugins:install({"geocouch", "http://127.0.0.1:8000", "1.0.0", [{"R15B03", "+XOJP6GSzmuO2qKdnjO+mWckXVs="}]}).
-% couch_plugins:install({"geocouch", "http://people.apache.org/~jan/", "couchdb1.2.x_v0.3.0-11-gd83ba22", [{"R15B03", "ZetgdHj2bY2w37buulWVf3USOZs="}]}).
-
-plugin_dir() ->
- couch_config:get("couchdb", "plugin_dir").
-
-log(T) ->
- ?LOG_DEBUG("[couch_plugins] ~p ~n", [T]).
-
-%% "geocouch", "http://localhost:8000/dist", "1.0.0"
--type plugin() :: {string(), string(), string(), list()}.
--spec install(plugin()) -> ok | {error, string()}.
-install({Name, _BaseUrl, Version, Checksums}=Plugin) ->
- log("Installing " ++ Name),
-
- {ok, LocalFilename} = download(Plugin),
- log("downloaded to " ++ LocalFilename),
-
- ok = verify_checksum(LocalFilename, Checksums),
- log("checksum verified"),
-
- ok = untargz(LocalFilename),
- log("extraction done"),
-
- ok = add_code_path(Name, Version),
- log("added code path"),
-
- ok = register_plugin(Name, Version),
- log("registered plugin"),
-
- load_config(Name, Version),
- log("loaded config"),
-
- ok.
-
-% Idempotent uninstall, if you uninstall a non-existant
-% plugin, you get an `ok`.
--spec uninstall(plugin()) -> ok | {error, string()}.
-uninstall({Name, _BaseUrl, Version, _Checksums}) ->
- % unload config
- ok = unload_config(Name, Version),
- log("config unloaded"),
-
- % delete files
- ok = delete_files(Name, Version),
- log("files deleted"),
-
- % delete code path
- ok = del_code_path(Name, Version),
- log("deleted code path"),
-
- % unregister plugin
- ok = unregister_plugin(Name),
- log("unregistered plugin"),
-
- % done
- ok.
-
-%% * * *
-
-
-%% Plugin Registration
-%% On uninstall:
-%% - add plugins/name = version to config
-%% On uninstall:
-%% - remove plugins/name from config
-
--spec register_plugin(string(), string()) -> ok.
-register_plugin(Name, Version) ->
- couch_config:set("plugins", Name, Version).
-
--spec unregister_plugin(string()) -> ok.
-unregister_plugin(Name) ->
- couch_config:delete("plugins", Name).
-
-%% * * *
-
-
-%% Load Config
-%% Parses <plugindir>/priv/default.d/<pluginname.ini> and applies
-%% the contents to the config system, or removes them on uninstall
-
--spec load_config(string(), string()) -> ok.
-load_config(Name, Version) ->
- loop_config(Name, Version, fun set_config/1).
-
--spec unload_config(string(), string()) -> ok.
-unload_config(Name, Version) ->
- loop_config(Name, Version, fun delete_config/1).
-
--spec loop_config(string(), string(), function()) -> ok.
-loop_config(Name, Version, Fun) ->
- lists:foreach(fun(File) -> load_config_file(File, Fun) end,
- filelib:wildcard(file_names(Name, Version))).
-
--spec load_config_file(string(), function()) -> ok.
-load_config_file(File, Fun) ->
- {ok, Config} = couch_config:parse_ini_file(File),
- lists:foreach(Fun, Config).
-
--spec set_config({{string(), string()}, string()}) -> ok.
-set_config({{Section, Key}, Value}) ->
- ok = couch_config:set(Section, Key, Value).
-
--spec delete_config({{string(), string()}, _Value}) -> ok.
-delete_config({{Section, Key}, _Value}) ->
- ok = couch_config:delete(Section, Key).
-
--spec file_names(string(), string()) -> string().
-file_names(Name, Version) ->
- filename:join(
- [plugin_dir(), get_file_slug(Name, Version),
- "priv", "default.d", "*.ini"]).
-
-%% * * *
-
-
-%% Code Path Management
-%% The Erlang code path is where the Erlang runtime looks for `.beam`
-%% files to load on, say, `application:load()`. Since plugin directories
-%% are created on demand and named after CouchDB and Erlang versions,
-%% we manage the Erlang code path semi-automatically here.
-
--spec add_code_path(string(), string()) -> ok | {error, bad_directory}.
-add_code_path(Name, Version) ->
- PluginPath = plugin_dir() ++ "/" ++ get_file_slug(Name, Version) ++ "/ebin",
- case code:add_path(PluginPath) of
- true -> ok;
- Else ->
- ?LOG_ERROR("Failed to add PluginPath: '~s'", [PluginPath]),
- Else
- end.
-
--spec del_code_path(string(), string()) -> ok | {error, atom()}.
-del_code_path(Name, Version) ->
- PluginPath = plugin_dir() ++ "/" ++ get_file_slug(Name, Version) ++ "/ebin",
- case code:del_path(PluginPath) of
- true -> ok;
- _Else ->
- ?LOG_DEBUG("Failed to delete PluginPath: '~s', ignoring", [PluginPath]),
- ok
- end.
-
-%% * * *
-
-
--spec untargz(string()) -> {ok, string()} | {error, string()}.
-untargz(Filename) ->
- % read .gz file
- {ok, GzData} = file:read_file(Filename),
- % gunzip
- log("unzipped"),
- TarData = zlib:gunzip(GzData),
- ok = filelib:ensure_dir(plugin_dir()),
- % untar
- erl_tar:extract({binary, TarData}, [{cwd, plugin_dir()}, keep_old_files]).
-
--spec delete_files(string(), string()) -> ok | {error, atom()}.
-delete_files(Name, Version) ->
- PluginPath = plugin_dir() ++ "/" ++ get_file_slug(Name, Version),
- mochitemp:rmtempdir(PluginPath).
-
-
-% downloads a pluygin .tar.gz into a local plugins directory
--spec download(string()) -> ok | {error, string()}.
-download({Name, _BaseUrl, Version, _Checksums}=Plugin) ->
- TargetFile = "/tmp/" ++ get_filename(Name, Version),
- case file_exists(TargetFile) of
- %% wipe and redownload
- true -> file:delete(TargetFile);
- _Else -> ok
- end,
- Url = get_url(Plugin),
- HTTPOptions = [
- {connect_timeout, 30*1000}, % 30 seconds
- {timeout, 30*1000} % 30 seconds
- ],
- % todo: windows
- Options = [
- {stream, TargetFile}, % /tmp/something
- {body_format, binary},
- {full_result, false}
- ],
- % todo: reduce to just httpc:request()
- case httpc:request(get, {Url, []}, HTTPOptions, Options) of
- {ok, _Result} ->
- log("downloading " ++ Url),
- {ok, TargetFile};
- Error -> Error
- end.
-
--spec verify_checksum(string(), list()) -> ok | {error, string()}.
-verify_checksum(Filename, Checksums) ->
-
- CouchDBVersion = couchdb_version(),
- case proplists:get_value(CouchDBVersion, Checksums) of
- undefined ->
- ?LOG_ERROR("[couch_plugins] Can't find checksum for CouchDB Version '~s'", [CouchDBVersion]),
- {error, no_couchdb_checksum};
- OTPChecksum ->
- OTPRelease = erlang:system_info(otp_release),
- case proplists:get_value(OTPRelease, OTPChecksum) of
- undefined ->
- ?LOG_ERROR("[couch_plugins] Can't find checksum for Erlang Version '~s'", [OTPRelease]),
- {error, no_erlang_checksum};
- Checksum ->
- do_verify_checksum(Filename, Checksum)
- end
- end.
-
--spec do_verify_checksum(string(), string()) -> ok | {error, string()}.
-do_verify_checksum(Filename, Checksum) ->
- ?LOG_DEBUG("Checking Filename: ~s", [Filename]),
- case file:read_file(Filename) of
- {ok, Data} ->
- ComputedChecksum = binary_to_list(base64:encode(crypto:sha(Data))),
- case ComputedChecksum of
- Checksum -> ok;
- _Else ->
- ?LOG_ERROR("Checksum mismatch. Wanted: '~p'. Got '~p'", [Checksum, ComputedChecksum]),
- {error, checksum_mismatch}
- end;
- Error -> Error
- end.
-
-
-%% utils
-
--spec get_url(plugin()) -> string().
-get_url({Name, BaseUrl, Version, _Checksums}) ->
- BaseUrl ++ "/" ++ get_filename(Name, Version).
-
--spec get_filename(string(), string()) -> string().
-get_filename(Name, Version) ->
- get_file_slug(Name, Version) ++ ".tar.gz".
-
--spec get_file_slug(string(), string()) -> string().
-get_file_slug(Name, Version) ->
- % OtpRelease does not include patch levels like the -1 in R15B03-1
- OTPRelease = erlang:system_info(otp_release),
- CouchDBVersion = couchdb_version(),
- string:join([Name, Version, OTPRelease, CouchDBVersion], "-").
-
--spec file_exists(string()) -> boolean().
-file_exists(Filename) ->
- does_file_exist(file:read_file_info(Filename)).
--spec does_file_exist(term()) -> boolean().
-does_file_exist({error, enoent}) -> false;
-does_file_exist(_Else) -> true.
-
-couchdb_version() ->
- couch_server:get_version(short).
-
-% installing a plugin:
-% - POST /_plugins -d {plugin-def}
-% - get plugin definition
-% - get download URL (matching erlang version)
-% - download archive
-% - match checksum
-% - untar-gz archive into a plugins dir
-% - code:add_path(“geocouch-{geocouch_version}-{erlang_version}/ebin”)
-% - [cp geocouch-{geocouch_version}-{erlang_version}/etc/ ]
-% - application:start(geocouch)
-% - register plugin in plugin registry
-
-% Plugin registry impl:
-% - _plugins database
-% - pro: known db ops
-% - con: no need for replication, needs to be system db etc.
-% - _config/plugins namespace in config
-% - pro: lightweight, fits rarely-changing nature better
-% - con: potentially not flexible enough
-
-
-
-% /geocouch
-% /geocouch/dist/
-% /geocouch/dist/geocouch-{geocouch_version}-{erlang_version}.tar.gz
-
-% tar.gz includes:
-% geocouch-{geocouch_version}-{erlang_version}/
-% geocouch-{geocouch_version}-{erlang_version}/ebin
-% [geocouch-{geocouch_version}-{erlang_version}/config/config.erlt]
-% [geocouch-{geocouch_version}-{erlang_version}/share/]
-
diff --git a/apps/couch_plugins/src/couch_plugins_httpd.erl b/apps/couch_plugins/src/couch_plugins_httpd.erl
deleted file mode 100644
index 4dabbb4b5..000000000
--- a/apps/couch_plugins/src/couch_plugins_httpd.erl
+++ /dev/null
@@ -1,65 +0,0 @@
-% Licensed 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.
--module(couch_plugins_httpd).
-
--export([handle_req/1]).
-
--include_lib("couch/include/couch_db.hrl").
-
-handle_req(#httpd{method='POST'}=Req) ->
- ok = couch_httpd:verify_is_server_admin(Req),
- couch_httpd:validate_ctype(Req, "application/json"),
-
- {PluginSpec} = couch_httpd:json_body_obj(Req),
- Url = binary_to_list(couch_util:get_value(<<"url">>, PluginSpec)),
- Name = binary_to_list(couch_util:get_value(<<"name">>, PluginSpec)),
- Version = binary_to_list(couch_util:get_value(<<"version">>, PluginSpec)),
- Delete = couch_util:get_value(<<"delete">>, PluginSpec),
- {Checksums0} = couch_util:get_value(<<"checksums">>, PluginSpec),
- Checksums = parse_checksums(Checksums0),
-
- Plugin = {Name, Url, Version, Checksums},
- case do_install(Delete, Plugin) of
- ok ->
- couch_httpd:send_json(Req, 202, {[{ok, true}]});
- Error ->
- ?LOG_DEBUG("Plugin Spec: ~p", [PluginSpec]),
- couch_httpd:send_error(Req, {bad_request, Error})
- end;
-% handles /_plugins/<pluginname>/<file>
-% serves <plugin_dir>/<pluginname>-<pluginversion>-<otpversion>-<couchdbversion>/<file>
-handle_req(#httpd{method='GET',path_parts=[_, Name0 | Path0]}=Req) ->
- Name = ?b2l(Name0),
- Path = lists:map(fun binary_to_list/1, Path0),
- OTPRelease = erlang:system_info(otp_release),
- PluginVersion = couch_config:get("plugins", Name),
- CouchDBVersion = couch_server:get_version(short),
- FullName = string:join([Name, PluginVersion, OTPRelease, CouchDBVersion], "-"),
- FullPath = filename:join([FullName, "priv", "www", string:join(Path, "/")]) ++ "/",
- ?LOG_DEBUG("Serving ~p from ~p", [FullPath, plugin_dir()]),
- couch_httpd:serve_file(Req, FullPath, plugin_dir());
-handle_req(Req) ->
- couch_httpd:send_method_not_allowed(Req, "POST").
-
-plugin_dir() ->
- couch_config:get("couchdb", "plugin_dir").
-do_install(false, Plugin) ->
- couch_plugins:install(Plugin);
-do_install(true, Plugin) ->
- couch_plugins:uninstall(Plugin).
-
-parse_checksums(Checksums) ->
- lists:map(fun({K, {V}}) ->
- {binary_to_list(K), parse_checksums(V)};
- ({K, V}) ->
- {binary_to_list(K), binary_to_list(V)}
- end, Checksums).