├── src ├── .gitignore ├── kappa.app.src ├── kappa.hrl ├── kappa_app.erl ├── kappa_sup.erl ├── kappa_edge_cache.erl ├── graphviz.erl ├── kappa_report.erl ├── kappa_check.erl ├── kappa_server.erl ├── abstract_code_analysis.erl ├── kappa.erl ├── klib_parallel.erl └── record_use_check.erl ├── .gitignore ├── .editorconfig ├── CHANGELOG.md ├── rebar.config ├── rebar.lock ├── .github ├── workflows │ ├── repolint.yml │ └── build.yml ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md ├── test ├── code_analysis_test_lib.erl ├── illegal_call_tests.erl ├── abstract_code_analysis_tests.erl └── record_use_check_tests.erl ├── bin ├── record_use_check_trigger.escript ├── kappa_ownership.escript └── kappa_diff ├── README.md └── LICENSE /src/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | rebar3 3 | priv/ 4 | doc/ 5 | *.idea/* 6 | *.iml 7 | erlang_ls.config 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | 3 | [*.erl,*.hrl,*.escript,rebar.config] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | charset = latin1 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {deps, [{getopt, "1.0.2"}]}. 2 | 3 | {xref_checks, 4 | [undefined_function_calls, 5 | undefined_functions, 6 | locals_not_used, 7 | %% exports_not_used, 8 | deprecated_function_calls, 9 | deprecated_functions 10 | ]}. 11 | 12 | {edoc_opts, [{preprocess, true}]}. 13 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | {"1.2.0", 2 | [{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.2">>},0}]}. 3 | [ 4 | {pkg_hash,[ 5 | {<<"getopt">>, <<"33D9B44289FE7AD08627DDFE1D798E30B2DA0033B51DA1B3A2D64E72CD581D02">>}]}, 6 | {pkg_hash_ext,[ 7 | {<<"getopt">>, <<"A0029AEA4322FB82A61F6876A6D9C66DC9878B6CB61FAA13DF3187384FD4EA26">>}]} 8 | ]. 9 | -------------------------------------------------------------------------------- /.github/workflows/repolint.yml: -------------------------------------------------------------------------------- 1 | name: Klarna repolint 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | # Checks-out the repository under $GITHUB_WORKSPACE 15 | - uses: actions/checkout@v2 16 | 17 | - name: Install dependencies 18 | run: npm install repolinter log-symbols 19 | 20 | # @TODO Remove when fixed 21 | - name: Fix missing dependency in repolint 22 | run: npm install is-windows 23 | 24 | - name: Use custom rules 25 | run: wget https://raw.githubusercontent.com/klarna-incubator/meta/master/repolint.json 26 | 27 | - name: Run repolint 28 | run: ./node_modules/.bin/repolinter $GITHUB_WORKSPACE 29 | -------------------------------------------------------------------------------- /src/kappa.app.src: -------------------------------------------------------------------------------- 1 | {application, kappa, 2 | [ {description, "Code analysis and owership toolbox"} 3 | , {vsn, "1.0"} 4 | , {registered, [kappa_server]} 5 | , {mod, {kappa_app, []}} 6 | , {env, [{ignore, [app_name, {behaviour, bo3}]}, 7 | {error_logger, fun logger:error/2}, 8 | 9 | {architecture_file, "architecture.eterm"}, 10 | {ownership_file, "team.eterm"}, 11 | {db_ownership_file, "tables.eterm"}, 12 | {record_field_use_file, "record_field_use.eterm"}, 13 | {approved_api_violations_file, "approved_api_violations.eterm"}, 14 | {approved_layer_violations_file, "approved_layer_violations.eterm"}, 15 | 16 | {edges_cache_file, "kappa.edges"} 17 | ]} 18 | , {applications, [kernel, stdlib, getopt]} 19 | ]}. 20 | -------------------------------------------------------------------------------- /src/kappa.hrl: -------------------------------------------------------------------------------- 1 | %%% 2 | %%% Copyright (c) 2021 Klarna Bank AB (publ) 3 | %%% 4 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %%% you may not use this file except in compliance with the License. 6 | %%% You may obtain a copy of the License at 7 | %%% 8 | %%% http://www.apache.org/licenses/LICENSE-2.0 9 | %%% 10 | %%% Unless required by applicable law or agreed to in writing, software 11 | %%% distributed under the License is distributed on an "AS IS" BASIS, 12 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %%% See the License for the specific language governing permissions and 14 | %%% limitations under the License. 15 | %%% 16 | -record(edge, { from 17 | , to 18 | , info = [] 19 | }). 20 | 21 | -record(cache, { mod_edges = [] 22 | , fun_edges = [] 23 | , exports = [] 24 | , load_orders = [] 25 | , non_upgrade_calls = [] 26 | , checksum = [] 27 | }). 28 | -------------------------------------------------------------------------------- /test/code_analysis_test_lib.erl: -------------------------------------------------------------------------------- 1 | %%% 2 | %%% Copyright (c) 2021 Klarna Bank AB (publ) 3 | %%% 4 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %%% you may not use this file except in compliance with the License. 6 | %%% You may obtain a copy of the License at 7 | %%% 8 | %%% http://www.apache.org/licenses/LICENSE-2.0 9 | %%% 10 | %%% Unless required by applicable law or agreed to in writing, software 11 | %%% distributed under the License is distributed on an "AS IS" BASIS, 12 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %%% See the License for the specific language governing permissions and 14 | %%% limitations under the License. 15 | %%% 16 | -module(code_analysis_test_lib). 17 | 18 | -export([erlang_code_to_abst/1]). 19 | 20 | erlang_code_to_abst(ErlangCode) -> 21 | erlang_code_to_abst(ErlangCode, 1, []). 22 | 23 | erlang_code_to_abst([], _Line, Acc) -> 24 | lists:reverse(Acc); 25 | erlang_code_to_abst(String, Line, Acc) -> 26 | {done, {ok, Tokens, NewLine}, Rest} = erl_scan:tokens([], String, Line), 27 | {ok, Abst} = erl_parse:parse_form(Tokens), 28 | erlang_code_to_abst(Rest, NewLine, [Abst | Acc]). 29 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this Klarna project 2 | 3 | Are you here to help with this Klarna project? Welcome! Please read the following to better understand how to ask questions or work on something. 4 | 5 | All members of our community are expected to follow our [Code of Conduct](CODE_OF_CONDUCT.md). Please make sure you are welcoming and friendly in all of our spaces. 6 | 7 | ## Get in touch 8 | 9 | - Report bugs, suggest features or view the source code on GitHub. 10 | - If you have any questions concerning this product, please contact developers@klarna.com. 11 | 12 | ## Contributing to development 13 | 14 | At Klarna, we strive toward achieving the highest possible quality for our 15 | products. Therefore, we require you to follow these guidelines if you wish 16 | to contribute. 17 | 18 | Your contribution has to meet the following criteria: 19 | 20 | - It is accompanied by a description regarding what has been changed and why. 21 | - Pull requests should implement a boxed change, meaning they should optimally not try to address many things at once. 22 | - All code and documentation must follow the style specified by 23 | the included configuration. 24 | - New features and bug fixes must have accompanying unit tests. 25 | - All unit tests should pass. 26 | -------------------------------------------------------------------------------- /src/kappa_app.erl: -------------------------------------------------------------------------------- 1 | %%% 2 | %%% Copyright (c) 2021 Klarna Bank AB (publ) 3 | %%% 4 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %%% you may not use this file except in compliance with the License. 6 | %%% You may obtain a copy of the License at 7 | %%% 8 | %%% http://www.apache.org/licenses/LICENSE-2.0 9 | %%% 10 | %%% Unless required by applicable law or agreed to in writing, software 11 | %%% distributed under the License is distributed on an "AS IS" BASIS, 12 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %%% See the License for the specific language governing permissions and 14 | %%% limitations under the License. 15 | %%% 16 | %%% @doc Kappa Application 17 | %% @private 18 | -module(kappa_app). 19 | -behaviour(application). 20 | 21 | %%%_* Exports ========================================================== 22 | -export([ start/2 23 | , stop/1 24 | ]). 25 | 26 | %%%_* Code ============================================================= 27 | %%%_* API -------------------------------------------------------------- 28 | start(_Type, _Args) -> 29 | kappa_sup:start_link(). 30 | 31 | stop(_State) -> 32 | ok. 33 | 34 | %%%_* Emacs ============================================================ 35 | %%% Local Variables: 36 | %%% allout-layout: t 37 | %%% erlang-indent-level: 2 38 | %%% End: 39 | -------------------------------------------------------------------------------- /bin/record_use_check_trigger.escript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %% -*- erlang -*- 3 | %%! +A0 4 | 5 | %%% 6 | %%% Copyright (c) 2021 Klarna Bank AB (publ) 7 | %%% 8 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 9 | %%% you may not use this file except in compliance with the License. 10 | %%% You may obtain a copy of the License at 11 | %%% 12 | %%% http://www.apache.org/licenses/LICENSE-2.0 13 | %%% 14 | %%% Unless required by applicable law or agreed to in writing, software 15 | %%% distributed under the License is distributed on an "AS IS" BASIS, 16 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | %%% See the License for the specific language governing permissions and 18 | %%% limitations under the License. 19 | %%% 20 | 21 | main([]) -> 22 | AppStrings = string:tokens(os:getenv("OUR_APPS"), " "), 23 | Directories = [code:lib_dir(list_to_atom(AppString), ebin) || AppString <- AppStrings], 24 | Issues = record_use_check:gather_violations(Directories), 25 | ReportableIssues = 26 | case os:getenv("BUILD") of 27 | "test" -> Issues; 28 | _ -> lists:filter(fun record_use_check:is_reportable_in_non_test_builds/1, Issues) 29 | end, 30 | lists:foreach(fun record_use_check:handle_violation/1, ReportableIssues), 31 | case lists:any(fun record_use_check:is_real_violation/1, ReportableIssues) of 32 | false -> ok; 33 | true -> halt(1) 34 | end. 35 | -------------------------------------------------------------------------------- /test/illegal_call_tests.erl: -------------------------------------------------------------------------------- 1 | %%% 2 | %%% Copyright (c) 2021 Klarna Bank AB (publ) 3 | %%% 4 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %%% you may not use this file except in compliance with the License. 6 | %%% You may obtain a copy of the License at 7 | %%% 8 | %%% http://www.apache.org/licenses/LICENSE-2.0 9 | %%% 10 | %%% Unless required by applicable law or agreed to in writing, software 11 | %%% distributed under the License is distributed on an "AS IS" BASIS, 12 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %%% See the License for the specific language governing permissions and 14 | %%% limitations under the License. 15 | %%% 16 | -module(illegal_call_tests). 17 | 18 | -include_lib("eunit/include/eunit.hrl"). 19 | 20 | illegal_call_test_() -> 21 | %% Use a foreach with no tests: we don't want to run any tests here, 22 | %% just have an illegal call to meck:new/1. Unfortunately, xref 23 | %% crashes if we want to check illegal calls to a function that 24 | %% either doesn't exist or not called from anywhere. 25 | %% 26 | %% So this is a deliberately created illegal call that needs to 27 | %% exist, so we can check that no more occurrences of this call 28 | %% exist in our code. 29 | { foreach 30 | , fun () -> meck:new(kappa) end 31 | , fun (_) -> meck:unload(kappa) end 32 | , [] 33 | }. 34 | 35 | %%% Local Variables: 36 | %%% erlang-indent-level: 2 37 | %%% End: 38 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - '*' 6 | pull_request: 7 | branches: 8 | - master 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | platform: [ubuntu-latest] 14 | otp-version: [21.3, 22.2, 23.0] 15 | runs-on: ${{ matrix.platform }} 16 | container: 17 | image: erlang:${{ matrix.otp-version }} 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | - name: Cache Hex packages 22 | uses: actions/cache@v1 23 | with: 24 | path: ~/.cache/rebar3/hex/hexpm/packages 25 | key: ${{ runner.os }}-hex-${{ hashFiles(format('{0}{1}', github.workspace, '/rebar.lock')) }} 26 | restore-keys: | 27 | ${{ runner.os }}-hex- 28 | - name: Cache Dialyzer PLTs 29 | uses: actions/cache@v1 30 | with: 31 | path: ~/.cache/rebar3/rebar3_*.plt 32 | key: ${{ runner.os }}-dialyzer-${{ hashFiles(format('{0}{1}', github.workspace, '/rebar.config')) }} 33 | restore-keys: | 34 | ${{ runner.os }}-dialyzer- 35 | - name: Compile 36 | run: rebar3 as test compile 37 | - name: Run EUnit Tests 38 | run: rebar3 eunit 39 | - name: Run CT Tests 40 | run: rebar3 ct 41 | - name: Store CT Logs 42 | uses: actions/upload-artifact@v1 43 | with: 44 | name: ct-logs 45 | path: _build/test/logs 46 | - name: Run Checks 47 | run: rebar3 do dialyzer, xref 48 | - name: Produce Documentation 49 | run: rebar3 edoc 50 | - name: Publish Documentation 51 | uses: actions/upload-artifact@v1 52 | with: 53 | name: edoc 54 | path: doc 55 | -------------------------------------------------------------------------------- /src/kappa_sup.erl: -------------------------------------------------------------------------------- 1 | %%% 2 | %%% Copyright (c) 2021 Klarna Bank AB (publ) 3 | %%% 4 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %%% you may not use this file except in compliance with the License. 6 | %%% You may obtain a copy of the License at 7 | %%% 8 | %%% http://www.apache.org/licenses/LICENSE-2.0 9 | %%% 10 | %%% Unless required by applicable law or agreed to in writing, software 11 | %%% distributed under the License is distributed on an "AS IS" BASIS, 12 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %%% See the License for the specific language governing permissions and 14 | %%% limitations under the License. 15 | %%% 16 | %% @private 17 | -module(kappa_sup). 18 | -behaviour(supervisor). 19 | 20 | %%%_* Exports ========================================================== 21 | %% API 22 | -export([ start_link/0 ]). 23 | 24 | %% Callbacks 25 | -export([ init/1 ]). 26 | 27 | %%%_* Defines ========================================================== 28 | -define(SERVER, ?MODULE). 29 | 30 | %%%_* Code ============================================================= 31 | 32 | %%%_* API -------------------------------------------------------------- 33 | start_link() -> 34 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 35 | 36 | %%%_* Callbacks -------------------------------------------------------- 37 | init([]) -> 38 | KappaServer = {kappa_server, {kappa_server, start_link, []}, 39 | permanent, 2000, worker, [kappa_server]}, 40 | {ok, {{one_for_one, 50, 10}, [ KappaServer ]}}. 41 | 42 | %%%_* Emacs ============================================================ 43 | %%% Local Variables: 44 | %%% allout-layout: t 45 | %%% erlang-indent-level: 2 46 | %%% End: 47 | -------------------------------------------------------------------------------- /bin/kappa_ownership.escript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %% -*- mode: erlang -*- 3 | %%! -pz lib/getopt/ebin lib/kappa/ebin 4 | %%% 5 | %%% Copyright (c) 2021 Klarna Bank AB (publ) 6 | %%% 7 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 8 | %%% you may not use this file except in compliance with the License. 9 | %%% You may obtain a copy of the License at 10 | %%% 11 | %%% http://www.apache.org/licenses/LICENSE-2.0 12 | %%% 13 | %%% Unless required by applicable law or agreed to in writing, software 14 | %%% distributed under the License is distributed on an "AS IS" BASIS, 15 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | %%% See the License for the specific language governing permissions and 17 | %%% limitations under the License. 18 | %%% 19 | -module(kappa_ownership). 20 | 21 | %%%_* Code ============================================================= 22 | %%%_* Entrypoint ------------------------------------------------------- 23 | main([]) -> usage(); 24 | main(Args) -> 25 | case getopt:parse(option_specs(), Args) of 26 | {ok, {Options, []}} -> execute(Options); 27 | {ok, {_, NonArgs}} -> usage_fail("Unknown argument: ~s~n", [hd(NonArgs)]); 28 | {error, {Rsn, Data}} -> usage_fail("~s: ~s~n", [getopt:error_reason(Rsn), Data]) 29 | end. 30 | 31 | %%%_* Internals -------------------------------------------------------- 32 | execute([{module, Module}|_]) -> report(show_owner, [Module]); 33 | execute([{layer, Name}|_]) -> report(show_layer, [Name]); 34 | execute([summary|_]) -> report(show_summary); 35 | execute([owners| _]) -> report(show_ownership); 36 | execute([{team, Team}| _]) -> report(show_team, [Team]); 37 | execute([{slack, Team}| _]) -> report(show_slack_handle, [Team]); 38 | execute([orphans| _]) -> report(show_orphans); 39 | execute([mutes| _]) -> report(show_mutes); 40 | execute([help|_]) -> usage(). 41 | 42 | report(Fun) -> report(Fun, []). 43 | 44 | report(Fun, Args) -> 45 | ErrorLoggerFun = fun(_, [_, _, Err]) -> erlang:throw(Err) end, 46 | try 47 | code:add_pathsz(filelib:wildcard("lib/*/ebin")), 48 | {ok,_} = kappa_server:start(ErrorLoggerFun), 49 | erlang:apply(kappa_report, Fun, Args) 50 | catch 51 | error:{badmatch,{error,{bad_return_value,{enoent,Filename}}}} -> 52 | fail("No such file or directory: ~s~n", [Filename]); 53 | error:Err:ST -> fail("Unknown error: ~p~n~p~n", [Err, ST]) 54 | end. 55 | 56 | option_specs() -> 57 | [ {module, $o,"owner", atom, "Find owner of given module/application"} 58 | , {layer, $l,"layer", atom, "Find layer of application or module"} 59 | , {team, $t,"team", atom, "Show applications owned by a team"} 60 | , {slack, $k,"slack", atom, "Show the slack handle(s) for a team"} 61 | , {owners, $T,"teams", undefined,"Show all teams and their applications"} 62 | , {orphans,$O,"orphans",undefined,"Show applications that lack owner"} 63 | , {mutes, $m,"mutes", undefined,"Show applications that lack api"} 64 | , {summary,$s,"summary",undefined,"Show summary of ownership and orphans"} 65 | , {help, $?,"help", undefined,"Show usage screen"} 66 | ]. 67 | 68 | usage_args() -> [option_specs(), filename:basename(escript:script_name())]. 69 | 70 | usage() -> erlang:apply(getopt, usage, usage_args()). 71 | 72 | usage_fail() -> erlang:apply(getopt, usage_fail, usage_args()). 73 | 74 | usage_fail(Fmt, Data) -> fail(Fmt, Data, fun usage_fail/0). 75 | 76 | fail(Fmt, Data) -> fail(Fmt, Data, fun() -> ignore end). 77 | 78 | fail(Fmt, Data, Fun) -> 79 | io:format(standard_error, Fmt, Data), 80 | Fun(), 81 | erlang:halt(1). 82 | 83 | %%%_* Emacs ============================================================ 84 | %%% Local Variables: 85 | %%% allout-layout: t 86 | %%% erlang-indent-level: 2 87 | %%% End: 88 | -------------------------------------------------------------------------------- /src/kappa_edge_cache.erl: -------------------------------------------------------------------------------- 1 | %%% 2 | %%% Copyright (c) 2021 Klarna Bank AB (publ) 3 | %%% 4 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %%% you may not use this file except in compliance with the License. 6 | %%% You may obtain a copy of the License at 7 | %%% 8 | %%% http://www.apache.org/licenses/LICENSE-2.0 9 | %%% 10 | %%% Unless required by applicable law or agreed to in writing, software 11 | %%% distributed under the License is distributed on an "AS IS" BASIS, 12 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %%% See the License for the specific language governing permissions and 14 | %%% limitations under the License. 15 | %%% 16 | %%% @doc Functions to handle kappa.edges cache files. 17 | -module(kappa_edge_cache). 18 | 19 | %% API 20 | -export([ read_cache_file/1 21 | , cache_to_file/2 22 | , pre_check_cache/2 23 | , checksum/1 24 | , check_cache/3 25 | , update_cache/3 26 | ]). 27 | 28 | %%% Includes =========================================================== 29 | 30 | -include("kappa.hrl"). 31 | 32 | %%% Definitions ======================================================== 33 | 34 | -define(cache_format_version, 2). 35 | %% The first version of the cache file did not contain any version 36 | %% number. So the first actual version number one may encounter in a 37 | %% file is 2. 38 | 39 | %%% API ================================================================ 40 | 41 | read_cache_file(FileName) -> 42 | case file:consult(FileName) of 43 | {ok, [ ?cache_format_version 44 | , ModEdges 45 | , FunEdges 46 | , Exports 47 | , LoadOrders 48 | , NonUpgradeCalls 49 | , Checksum 50 | ]} -> 51 | #cache{ mod_edges = ModEdges 52 | , fun_edges = FunEdges 53 | , exports = Exports 54 | , load_orders = LoadOrders 55 | , non_upgrade_calls = NonUpgradeCalls 56 | , checksum = Checksum 57 | }; 58 | _ -> 59 | #cache{} 60 | end. 61 | 62 | cache_to_file(Cache, CacheFile) -> 63 | String = [io_lib:format("\n%%% ~s\n~200p.\n", [Field, Value]) 64 | || {Field, Value} <- 65 | [ {version, ?cache_format_version} 66 | , {mod_edges, lists:sort(Cache#cache.mod_edges)} 67 | , {fun_edges, lists:sort(Cache#cache.fun_edges)} 68 | , {exports, lists:sort(Cache#cache.exports)} 69 | , {load_orders, lists:sort(Cache#cache.load_orders)} 70 | , {non_upgrade_calls, lists:sort(Cache#cache.non_upgrade_calls)} 71 | , {checksum, Cache#cache.checksum} % orddict, already sorted 72 | ] 73 | ], 74 | write_file(CacheFile, 75 | ["%%% -*- mode: erlang; coding: latin-1; -*-\n", String]). 76 | 77 | pre_check_cache(Beams, #cache{checksum=Checksum} = Cache) -> 78 | %% Check that all beams are in the cache, and that all cache files 79 | %% are still present. Otherwise return an empty cache. This could be 80 | %% made more efficiently, but we do not add/remove files that often, 81 | %% so it is ok. 82 | KeySet = ordsets:from_list(orddict:fetch_keys(Checksum)), 83 | BeamSet = ordsets:from_list(Beams), 84 | case KeySet =:= BeamSet of 85 | true -> Cache; 86 | false -> #cache{} 87 | end. 88 | 89 | checksum(Term) -> 90 | erlang:md5(term_to_binary(Term)). 91 | 92 | check_cache(File, Current, #cache{checksum=Checksums}) -> 93 | case orddict:find(File, Checksums) of 94 | {ok, C} when C =:= Current -> ok; 95 | _ -> stale 96 | end. 97 | 98 | update_cache(File, Current, #cache{checksum=Checksums} = Cache) -> 99 | Cache#cache{checksum=orddict:store(File, Current, Checksums)}. 100 | 101 | %%% Helper functions =================================================== 102 | 103 | write_file(File, Payload) -> 104 | case file:write_file(File, Payload) of 105 | ok -> ok; 106 | {error, Reason} -> erlang:error({write_error, {File, Reason}}) 107 | end. 108 | 109 | %%% Local Variables: 110 | %%% erlang-indent-level: 2 111 | %%% End: 112 | -------------------------------------------------------------------------------- /src/graphviz.erl: -------------------------------------------------------------------------------- 1 | %%% 2 | %%% Copyright (c) 2021 Klarna Bank AB (publ) 3 | %%% 4 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %%% you may not use this file except in compliance with the License. 6 | %%% You may obtain a copy of the License at 7 | %%% 8 | %%% http://www.apache.org/licenses/LICENSE-2.0 9 | %%% 10 | %%% Unless required by applicable law or agreed to in writing, software 11 | %%% distributed under the License is distributed on an "AS IS" BASIS, 12 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %%% See the License for the specific language governing permissions and 14 | %%% limitations under the License. 15 | %%% 16 | -module(graphviz). 17 | 18 | -export([ to_dot/2 19 | , to_dot/3 20 | , to_dot/4 21 | , to_dot/5 22 | , run_dot/3 23 | ]). 24 | 25 | %%%============================================================================= 26 | %%% 27 | %%% Dot interface. 28 | %%% 29 | %%% Typical workflow: 30 | %%% 1. Create dot string using to_dot/2-5, e.g.: 31 | %%% graphviz:to_dot("G", % graph name 32 | %%% [{a, b}, {b, c}, {a, c}], % edge list 33 | %%% [{a, [{style, filled}]}], % named-node options 34 | %%% [{rankdir, "LR"}], % graph options 35 | %%% [{node, [ {shape, box} % general options 36 | %%% , {style, rounded}]}]). 37 | %%% 2. Make modifications to the string if you know what you are doing. 38 | %%% 3. Run dot to make your final output (gif, ps, etc.) using run_dot/3. 39 | %%% 4. ... 40 | %%% 5. Profit 41 | %%% 42 | %%% Preferably, use atoms, strings or integers as nodenames. It can 43 | %%% work for nasty characters (e.g., tuples, records), but don't bet 44 | %%% your life on it. 45 | %%% 46 | 47 | run_dot(Dotfile, OutFile, Type) -> 48 | String = io_lib:format("dot ~s -o ~s ~s", 49 | [format_dot_type(Type), OutFile, Dotfile]), 50 | case os:cmd(String) of 51 | "" -> ok; 52 | Error -> erlang:error(Error) 53 | end. 54 | 55 | format_dot_type(gif) -> "-Tgif"; 56 | format_dot_type(pdf) -> "-Tpdf"; 57 | format_dot_type(png) -> "-Tpng"; 58 | format_dot_type(ps) -> "-Tps"; 59 | format_dot_type(svg) -> "-Tsvg". 60 | 61 | -type dot_attr() :: {Name :: atom(), Val :: any()}. 62 | 63 | -type dot_edge() :: ({From :: any(), To :: any()} 64 | | {From :: any(), To :: any(), [dot_attr()]}). 65 | 66 | -type dot_node_opts() :: {Name :: _, [dot_attr()]}. 67 | 68 | -type dot_graph_opt() :: {OptName :: atom(), OptVal :: any()}. 69 | 70 | -spec to_dot(string(), [dot_edge()]) -> string(). 71 | 72 | to_dot(GraphName, EdgeList) -> 73 | to_dot(GraphName, EdgeList, []). 74 | 75 | -spec to_dot(string(), [dot_edge()], [dot_node_opts()]) -> string(). 76 | 77 | to_dot(GraphName, EdgeList, NodeOpts) -> 78 | to_dot(GraphName, EdgeList, NodeOpts, []). 79 | 80 | -spec to_dot(string(), [dot_edge()], [dot_node_opts()], [dot_graph_opt()]) -> 81 | string(). 82 | 83 | to_dot(GraphName, EdgeList, NodeOpts, GraphOpts) -> 84 | to_dot(GraphName, EdgeList, NodeOpts, GraphOpts, []). 85 | 86 | -spec to_dot(GraphName :: string(), 87 | EdgeList :: [dot_edge()], 88 | NodeOpts :: [dot_node_opts()], 89 | GraphOpts :: [dot_graph_opt()], 90 | GenOpts :: [dot_node_opts()]) 91 | -> string(). 92 | 93 | to_dot(GraphName, EdgeList, NodeOpts, GraphOpts, GenOpts) -> 94 | S0 = [format_dot_graph_opt(O) || O <- GraphOpts], 95 | S1 = [format_dot_gen_opt(O) || O <- GenOpts], 96 | S2 = [format_dot_node(N) || N <- NodeOpts], 97 | S3 = [format_dot_edge(E) || E <- EdgeList], 98 | S4 = io_lib:format("digraph ~p {\n~s~s~s~s}\n", 99 | [to_string(GraphName), S0, S1, S2, S3]), 100 | lists:flatten(S4). 101 | 102 | format_dot_edge({From, To}) -> 103 | io_lib:format("~p -> ~p\n", [to_string(From), to_string(To)]); 104 | format_dot_edge({From, To, Attrs}) -> 105 | io_lib:format("~p -> ~p [~s]\n", 106 | [to_string(From), to_string(To), format_dot_attrs(Attrs)]). 107 | 108 | format_dot_node({Node, Attrs}) -> 109 | io_lib:format("~p [~s]\n", [to_string(Node), format_dot_attrs(Attrs)]). 110 | 111 | format_dot_attrs(List) -> 112 | lists:map(fun format_dot_attr/1, List). 113 | 114 | format_dot_attr({Name, Val}) -> 115 | io_lib:format("~p=~p", [to_string(Name), to_string(Val)]). 116 | 117 | format_dot_graph_opt({same_rank, List}) -> 118 | Nodes = string:join([io_lib:format("~p", [to_string(Node)]) || Node <- List], 119 | " "), 120 | io_lib:format("{rank=same; ~s}\n", [Nodes]); 121 | format_dot_graph_opt({Opt, Val}) -> 122 | io_lib:format("graph[~s=~s];\n", [to_string(Opt), to_string(Val)]). 123 | 124 | format_dot_gen_opt({Opt, List}) -> 125 | Attrs = string:join([format_dot_gen_attr(A) || A <- List], ","), 126 | io_lib:format("~s[~s];\n", [atom_to_list(Opt), Attrs]). 127 | 128 | format_dot_gen_attr({K, V}) -> 129 | io_lib:format("~p=~p", [K, V]). 130 | 131 | to_string(What) -> 132 | [X || X <- lists:flatten(io_lib:format("~p", [What])), X =/= $\"]. 133 | 134 | %%% Local Variables: 135 | %%% erlang-indent-level: 2 136 | %%% End: 137 | -------------------------------------------------------------------------------- /bin/kappa_diff: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # Copyright (c) 2021 Klarna Bank AB (publ) 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | # This is a script to automatically check for kappa violations in current 20 | # branch and on master/staging branch 21 | # It will output a diff and also the number of kappa violations in current 22 | # branch and on master/staging branch 23 | 24 | # Saveguard to not use unbound vars 25 | set -u 26 | # Prevent error snowballing (make sure to return true from successful operations) 27 | set -e 28 | 29 | HOME_DIR="$( cd "$( dirname "$0" )" && pwd )" 30 | 31 | function _usage () { 32 | echo 33 | echo "Usage: $(basename $0) [master|staging]" 34 | echo 35 | echo "If you don't specify a reference branch, it will be automatically" 36 | echo "detected based on whether staging is contanied by your current HEAD" 37 | echo 38 | exit 1 39 | } 40 | 41 | function _detect_reference_branch() { 42 | if [ -z "$(git branch -r --merged | grep /staging$)" ]; then 43 | _RETURN="master" 44 | else 45 | _RETURN="staging" 46 | fi 47 | } 48 | 49 | function _parse_input_arg() { 50 | case "$1" in 51 | "master"|"staging") 52 | _RETURN="$1" 53 | ;; 54 | *) 55 | _usage; exit 1 56 | ;; 57 | esac 58 | } 59 | 60 | 61 | # Parse input arguments 62 | case "$#" in 63 | "0") 64 | _detect_reference_branch 65 | BASE_BRANCH=$_RETURN 66 | ;; 67 | "1") 68 | _parse_input_arg $1 69 | BASE_BRANCH=$_RETURN 70 | ;; 71 | *) 72 | _usage; exit 1 73 | esac 74 | 75 | echo "*** Using $BASE_BRANCH as reference" 76 | 77 | # Checking for the kappa_check script 78 | KAPPA="$HOME_DIR/kappa_check" 79 | if ! [ -e "$KAPPA" ]; then 80 | echo "Unable to find $KAPPA. Unless you deleted it, this is a bug." 81 | exit 1 82 | fi 83 | 84 | KAPPA_BASE_API=`mktemp /tmp/kappa-base-api.XXXXX` 85 | KAPPA_BASE_LAYER=`mktemp /tmp/kappa-base-layer.XXXXX` 86 | 87 | KAPPA_CURRENT_API=`mktemp /tmp/kappa-current-api.XXXXX` 88 | KAPPA_CURRENT_LAYER=`mktemp /tmp/kappa-current-layer.XXXXX` 89 | 90 | KAPPA_DIFF_API=`mktemp /tmp/kappa-diff-api.XXXXX` 91 | KAPPA_DIFF_LAYER=`mktemp /tmp/kappa-diff-layer.XXXXX` 92 | KAPPA_LINK_BASE="http://jenkins.internal.machines:8080/jenkins/view/hive/job/kappa-check-${BASE_BRANCH}-all/lastSuccessfulBuild/artifact/build_artifacts" 93 | KAPPA_LINK_API="${KAPPA_LINK_BASE}/kappa_api.txt" 94 | KAPPA_LINK_LAYER="${KAPPA_LINK_BASE}/kappa_layer.txt" 95 | 96 | echo "*** Fetching kappa violations for ${BASE_BRANCH}" 97 | curl -s "${KAPPA_LINK_API}" | sort > "${KAPPA_BASE_API}" || true 98 | curl -s "${KAPPA_LINK_LAYER}" | sort > "${KAPPA_BASE_LAYER}" || true 99 | if [ ! -s "${KAPPA_BASE_API}" ]; then 100 | echo "Not possible to download kappa violations for ${BASE_BRANCH} from: ${KAPPA_LINK_API}" 101 | exit 1 102 | fi 103 | echo "*** Running kappa api check on current branch" 104 | $KAPPA api | sort > "${KAPPA_CURRENT_API}" 105 | echo "*** Running kappa layers check on current branch" 106 | $KAPPA layers | sort > "${KAPPA_CURRENT_LAYER}" 107 | echo "=== Kappa Check Diff ===================================================" 108 | # remove hashes from diff for readability 109 | diff "${KAPPA_BASE_API}" "${KAPPA_CURRENT_API}" | grep -e "^[\>\<]" > "${KAPPA_DIFF_API}" && true 110 | diff "${KAPPA_BASE_LAYER}" "${KAPPA_CURRENT_LAYER}" | grep -e "^[\>\<]" > "${KAPPA_DIFF_LAYER}" && true 111 | BASE_VIOLATIONS_API=`cat ${KAPPA_BASE_API} | wc -l` 112 | BASE_VIOLATIONS_LAYER=`cat ${KAPPA_BASE_LAYER} | wc -l` 113 | CURRENT_VIOLATIONS_API=`cat ${KAPPA_CURRENT_API} | wc -l` 114 | CURRENT_VIOLATIONS_LAYER=`cat ${KAPPA_CURRENT_LAYER} | wc -l` 115 | API_INCREASE=`echo "${CURRENT_VIOLATIONS_API} - ${BASE_VIOLATIONS_API}" | bc -l` 116 | LAYER_INCREASE=`echo "${CURRENT_VIOLATIONS_LAYER} - ${BASE_VIOLATIONS_LAYER}" | bc -l` 117 | echo "\`diff ${BASE_BRANCH} current\`:" 118 | echo "API:" 119 | cat "${KAPPA_DIFF_API}" 120 | echo "LAYER:" 121 | cat "${KAPPA_DIFF_LAYER}" 122 | echo "========================================================================" 123 | if [ "${API_INCREASE}" -le "0" ] && [ "${LAYER_INCREASE}" -le "0" ]; then 124 | if [ "${API_INCREASE}" -lt "0" ] || [ "${LAYER_INCREASE}" -lt "0" ]; then 125 | echo "Congrats, kappa violation count is decreased!" 126 | echo "API net diff : ${API_INCREASE}" 127 | echo "Layer net diff: ${LAYER_INCREASE}" 128 | fi 129 | echo "NB! The check might work incorrectly if the branch doesn't contain the latest ${BASE_BRANCH}" 130 | echo "STATUS: OK" 131 | else 132 | if [ "${API_INCREASE}" -gt "0" ]; then 133 | echo "Branch has more api violations than ${BASE_BRANCH} (net diff: ${API_INCREASE})" 134 | fi 135 | if [ "${LAYER_INCREASE}" -gt "0" ]; then 136 | echo "Branch has more layer violations than ${BASE_BRANCH} (net diff: ${LAYER_INCREASE})" 137 | fi 138 | echo "NB! The check might work incorrectly if the branch doesn't contain the latest ${BASE_BRANCH}" 139 | echo "STATUS: FAILED" 140 | exit 1 141 | fi 142 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, political beliefs, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | developers@klarna.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /src/kappa_report.erl: -------------------------------------------------------------------------------- 1 | %%% 2 | %%% Copyright (c) 2021 Klarna Bank AB (publ) 3 | %%% 4 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 5 | %%% you may not use this file except in compliance with the License. 6 | %%% You may obtain a copy of the License at 7 | %%% 8 | %%% http://www.apache.org/licenses/LICENSE-2.0 9 | %%% 10 | %%% Unless required by applicable law or agreed to in writing, software 11 | %%% distributed under the License is distributed on an "AS IS" BASIS, 12 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | %%% See the License for the specific language governing permissions and 14 | %%% limitations under the License. 15 | %%% 16 | %%% @doc Klarna Application Analytics. 17 | %%% Functions to generate and print simple human-readable reports from the kappa 18 | %%% specification of the system. 19 | %% @private 20 | -module(kappa_report). 21 | 22 | %%%_* Exports ========================================================== 23 | -export([ show_layer/1 24 | , show_mutes/0 25 | , show_orphans/0 26 | , show_owner/1 27 | , show_ownership/0 28 | , show_slack_handle/1 29 | , show_summary/0 30 | , show_team/1 31 | ]). 32 | 33 | %%%_* Defines ========================================================== 34 | -define(OUTPUT_DONE, output). 35 | -define(NO_OUTPUT, none). 36 | 37 | %%%_* Types ============================================================ 38 | %% State for capturing if we have alreadu done any output. Used 39 | %% to possibly insert newlines between sections. 40 | -type output_state() :: ?NO_OUTPUT | ?OUTPUT_DONE. 41 | 42 | %%%_* Code ============================================================= 43 | %%%_* API -------------------------------------------------------------- 44 | %% @doc Show layer of specific application. 45 | -spec show_layer(Application::atom()) -> ok. 46 | 47 | show_layer(Application) -> 48 | case kappa:get_layer(Application) of 49 | undefined -> io:format("undefined~n"); 50 | {Layer, Allowed} -> 51 | io:format("Layer: ~p~nAllowed to call: ~p~n", [Layer, Allowed]) 52 | end. 53 | 54 | %% @doc Output summary of teams and the applications they own. 55 | -spec show_ownership() -> output_state(). 56 | 57 | show_ownership() -> 58 | output_ownership(?NO_OUTPUT). 59 | 60 | %% @doc Output a summary of ownership including any applications that 61 | %% currently lack an owner. 62 | -spec show_summary() -> output_state(). 63 | 64 | show_summary() -> 65 | OutState = output_orphans(?NO_OUTPUT), 66 | output_ownership(OutState). 67 | 68 | %% @doc Output list of applications without owners. 69 | -spec show_orphans() -> output_state(). 70 | 71 | show_orphans() -> 72 | output_orphans(?NO_OUTPUT). 73 | 74 | %% @doc Output list of applications without apis. 75 | -spec show_mutes() -> output_state(). 76 | 77 | show_mutes() -> 78 | output_mutes(?NO_OUTPUT). 79 | 80 | %% @doc Show owner of a specific module. 81 | -spec show_owner(Module::atom()) -> ok. 82 | 83 | show_owner(Module) -> 84 | io:format("~p~n", [kappa:get_owner(Module)]). 85 | 86 | %% @doc Show applications owned by specific team 87 | -spec show_team(atom()) -> output_state(). 88 | 89 | show_team(Team) -> 90 | output_ownership(get_descriptions([Team]), ?NO_OUTPUT). 91 | 92 | %% @doc Show the slack handle(s) for a specific team 93 | -spec show_slack_handle(atom()) -> ok. 94 | show_slack_handle(Team) -> 95 | Props = kappa:get_properties(Team), 96 | SlackHandles = proplists:get_value(slack_handle, Props, []), 97 | lists:foreach(fun(Handle) -> io:format("~s~n", [Handle]) end, 98 | SlackHandles). 99 | 100 | %%%_* Internals -------------------------------------------------------- 101 | %% @doc Get descrptions for teams listed. Each description consists 102 | %% of team name, alarm email(s) and a list of applications. 103 | -spec get_descriptions([atom()]) -> [{atom(), list(), list()}]. 104 | 105 | get_descriptions(Owners) -> 106 | [{Owner, 107 | kappa:get_properties(Owner), 108 | kappa:get_assets(Owner, apps)} 109 | || Owner <- Owners]. 110 | 111 | %% @doc Output descriptions for every team that own applications. 112 | -spec output_ownership(output_state()) -> output_state(). 113 | 114 | output_ownership(State) -> 115 | output_ownership(get_descriptions(kappa:get_owners()), State). 116 | 117 | %% @doc Output descriptions for teams listed. Teams that 118 | %% do not own any applications are not shown. 119 | -spec output_ownership(Descriptions::[{atom(), list(), list()}], output_state()) 120 | -> output_state(). 121 | 122 | output_ownership(Descriptors, OutState) -> 123 | Keep = fun({_, _, Apps}) -> Apps =/= [] end, 124 | case lists:filter(Keep, Descriptors) of 125 | [] -> OutState; 126 | Output -> 127 | F = fun({Owner, Properties, Apps}, State) -> 128 | maybe_nl(State), 129 | io:format("Team: ~p~n", [Owner]), 130 | lists:foreach(fun({Prop, Value}) -> 131 | io:format(" ~p: ~p~n", [Prop, Value]) 132 | end, 133 | Properties), 134 | io:format(" Applications (~p): ~p~n", [length(Apps), Apps]), 135 | ?OUTPUT_DONE 136 | end, 137 | lists:foldl(F, OutState, Output) 138 | end. 139 | 140 | %% @doc Output list of orphaned applications with header. Nothing is 141 | %% output if there are no orphans. 142 | -spec output_orphans([atom()], output_state()) -> output_state(). 143 | 144 | output_orphans([], State) -> State; 145 | output_orphans(Orphans, State) -> 146 | maybe_nl(State), 147 | io:format("Orphaned applications: ~p~n", [Orphans]), 148 | ?OUTPUT_DONE. 149 | 150 | output_orphans(State) -> 151 | output_orphans(kappa:get_assets(undefined, apps), State). 152 | 153 | output_mutes([], State) -> State; 154 | output_mutes(Mutes, State) -> 155 | maybe_nl(State), 156 | lists:foreach(fun({Team, Apps}) -> 157 | io:format("~p: ~p~n", [Team, Apps]) 158 | end, 159 | Mutes), 160 | ?OUTPUT_DONE. 161 | 162 | output_mutes(State) -> 163 | MutesByTeam = collect_by_team(kappa:get_mutes()), 164 | output_mutes(MutesByTeam, State). 165 | 166 | collect_by_team(Mutes) -> 167 | Dict = dict:new(), 168 | D2 = lists:foldl(fun({Team, App}, D) -> dict:append(Team, App, D) end, 169 | Dict, 170 | Mutes), 171 | lists:sort(dict:to_list(D2)). 172 | 173 | %% @doc Output a nl if we have previous output. 174 | -spec maybe_nl(output_state()) -> ok. 175 | 176 | maybe_nl(?OUTPUT_DONE) -> io:nl(); 177 | maybe_nl(?NO_OUTPUT) -> ok. 178 | 179 | %%%_* Emacs ============================================================ 180 | %%% Local Variables: 181 | %%% allout-layout: t 182 | %%% erlang-indent-level: 2 183 | %%% End: 184 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kappa (Klarna Application Analytics) 2 | > Keep a huge Erlang code base, developed by multiple teams, in a manageable state. 3 | 4 | [![Build Status][ci-image]][ci-url] 5 | [![License][license-image]][license-url] 6 | [![Developed at Klarna][klarna-image]][klarna-url] 7 | 8 | ## Features 9 | 10 | ### Code and database table ownership 11 | 12 | Kappa can tell you which team owns this particular app / module / 13 | database table and how they can be contacted (e.g. to send an alarm). 14 | 15 | ### Application API modules 16 | 17 | Restrict inter-app calls to only a set of API-modules. Calls from one 18 | app to other app's _private_ modules are not allowed. 19 | 20 | ### Restricted record fields usage 21 | 22 | Direct access to records defined in header files makes it hard to 23 | change the record's structure. Kappa allows only a defined set of 24 | modules to access specific record's fields directly. 25 | 26 | ### Code Layers 27 | 28 | Divide your apps into multiple layers (e.g. external interfaces and 29 | APIs / business logic layer / database level / system utilities / 30 | test-only / external dependencies, etc.) and apply restrictions on 31 | calls between applications that belong to different layers (allowed at 32 | all? if allowed, in which direction?) 33 | 34 | > :warning: Most of the checks rely on the presence of `debug_info` compiler option! 35 | 36 | ## Get Started 37 | 38 | rebar3 escriptize 39 | _build/default/bin/kappa 40 | 41 | Usage: kappa [] [-a []] [-m []] [-x []] 42 | [-f []] [-v []] [-r []] 43 | [-c []] [-g []] [-G []] 44 | [-p []] [-?] [name ...] 45 | 46 | api: report non api call violations 47 | layers: report layer violations 48 | all: report api and layer violations 49 | summary: print a one line summary of all violations in 50 | the system. Options other than -p are ignored. 51 | The format is: 52 |