├── .circleci ├── config.yml └── gpg.private.enc ├── .envrc ├── .git-crypt ├── .gitattributes └── keys │ └── default │ └── 0 │ ├── 41D2606F66C3FF28874362B61A16916844CE9D82.gpg │ ├── 933E3994686DC15C99D1369844037399AEDB1D8D.gpg │ ├── CBDE053F80475F8C49467ED687CE3CAE7DB712F2.gpg │ └── D164A61C69E23C0F74475FBE5FFE76AD095FCA07.gpg ├── .gitattributes ├── .github └── dependabot.yml ├── .gitignore ├── .java-version ├── .rubocop.yml ├── .ruby-version ├── .tool-versions ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.md ├── README.md ├── Rakefile ├── TODO.md ├── assets └── diagrams.graffle ├── check-fns ├── data-source │ ├── dev │ │ └── user.clj │ ├── project.clj │ ├── src │ │ └── salutem │ │ │ └── check_fns │ │ │ └── data_source │ │ │ └── core.clj │ └── test │ │ ├── integration │ │ └── salutem │ │ │ └── check_fns │ │ │ └── data_source │ │ │ └── timeout_test.clj │ │ ├── shared │ │ └── salutem │ │ │ └── test │ │ │ └── support │ │ │ └── jdbc.clj │ │ └── unit │ │ └── salutem │ │ └── check_fns │ │ └── data_source │ │ └── core_test.clj ├── http-endpoint │ ├── dev │ │ └── user.clj │ ├── project.clj │ ├── src │ │ └── salutem │ │ │ └── check_fns │ │ │ └── http_endpoint │ │ │ └── core.clj │ └── test │ │ ├── integration │ │ └── salutem │ │ │ └── check_fns │ │ │ └── http_endpoint │ │ │ ├── async_test.clj │ │ │ └── timeout_test.clj │ │ ├── shared │ │ └── salutem │ │ │ └── test │ │ │ └── support │ │ │ └── ports.clj │ │ └── unit │ │ └── salutem │ │ └── check_fns │ │ └── http_endpoint │ │ └── core_test.clj └── project.clj ├── config ├── gpg │ ├── jonas.gpg.public │ ├── liam.gpg.public │ └── toby.gpg.public ├── linter.clj └── secrets │ ├── ci │ ├── encryption.passphrase │ ├── gpg.private │ ├── gpg.public │ ├── gpg.uid │ ├── ssh.private │ └── ssh.public │ ├── circle_ci │ └── config.yaml │ ├── clojars │ ├── credentials.clj │ └── credentials.clj.gpg │ └── github │ └── config.yaml ├── core ├── dev │ └── user.clj ├── project.clj ├── src │ └── salutem │ │ ├── core.clj │ │ └── core │ │ ├── checks.clj │ │ ├── maintenance.clj │ │ ├── registry.clj │ │ ├── results.clj │ │ └── time.clj └── test │ ├── performance │ └── salutem │ │ └── core │ │ ├── maintenance │ │ └── performance_test.clj │ │ └── registry │ │ └── performance_test.clj │ ├── shared │ └── salutem │ │ └── test │ │ └── support │ │ ├── async.clj │ │ ├── data.clj │ │ └── time.clj │ └── unit │ └── salutem │ └── core │ ├── checks_test.clj │ ├── maintenance_test.clj │ ├── registry_test.clj │ ├── results_test.clj │ └── time_test.clj ├── docs ├── 01-getting-started.html ├── 01-getting-started.md ├── 02-check-functions.html ├── 02-check-functions.md ├── css │ ├── default.css │ └── highlight.css ├── getting-started.html ├── images │ ├── domain-model.png │ └── maintenance-pipeline.png ├── index.html ├── js │ ├── highlight.min.js │ ├── jquery.min.js │ └── page_effects.js ├── salutem.check-fns.data-source.core.html ├── salutem.check-fns.http-endpoint.core.html ├── salutem.core.checks.html ├── salutem.core.html ├── salutem.core.maintenance.html ├── salutem.core.registry.html ├── salutem.core.results.html └── salutem.core.time.html ├── go ├── parent └── project.clj ├── project.clj └── scripts └── ci ├── common ├── configure-clojars.sh ├── configure-git.sh ├── install-git-crypt.sh ├── install-gpg-key.sh ├── install-openjdk.sh └── upgrade-gpg.sh └── steps ├── build.sh ├── merge-pull-request.sh ├── prerelease.sh ├── release.sh └── test.sh /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | slack: circleci/slack@4.8.3 5 | 6 | ruby_container: &ruby_container 7 | image: ruby:3.1.1 8 | 9 | postgres_container: &postgres_container 10 | image: cimg/postgres:11.12 11 | environment: 12 | POSTGRES_DB: test 13 | POSTGRES_PASSWORD: test-password 14 | POSTGRES_USER: tester 15 | 16 | build_containers: &build_containers 17 | resource_class: medium 18 | docker: 19 | - <<: *ruby_container 20 | 21 | test_containers: &test_containers 22 | resource_class: medium 23 | docker: 24 | - <<: *ruby_container 25 | - <<: *postgres_container 26 | 27 | slack_context: &slack_context 28 | context: 29 | - slack 30 | 31 | only_main: &only_main 32 | filters: 33 | branches: 34 | only: 35 | - main 36 | 37 | only_dependabot: &only_dependabot 38 | filters: 39 | branches: 40 | only: 41 | - /^dependabot.*/ 42 | 43 | only_main_and_dependabot: &only_main_and_dependabot 44 | filters: 45 | branches: 46 | only: 47 | - main 48 | - /^dependabot.*/ 49 | 50 | commands: 51 | notify: 52 | steps: 53 | - when: 54 | condition: 55 | matches: 56 | pattern: "^dependabot.*" 57 | value: << pipeline.git.branch >> 58 | steps: 59 | - slack/notify: 60 | event: fail 61 | channel: builds-dependabot 62 | template: SLACK_FAILURE_NOTIFICATION 63 | - slack/notify: 64 | event: pass 65 | channel: builds-dependabot 66 | template: SLACK_SUCCESS_NOTIFICATION 67 | - when: 68 | condition: 69 | matches: 70 | pattern: "^(?!dependabot).*" 71 | value: << pipeline.git.branch >> 72 | steps: 73 | - slack/notify: 74 | event: fail 75 | channel: dev 76 | template: SLACK_FAILURE_NOTIFICATION 77 | - slack/notify: 78 | event: pass 79 | channel: builds 80 | template: SLACK_SUCCESS_NOTIFICATION 81 | configure_build_tools: 82 | steps: 83 | - run: ./scripts/ci/common/install-openjdk.sh 84 | configure_secrets_tools: 85 | steps: 86 | - run: ./scripts/ci/common/upgrade-gpg.sh 87 | - run: ./scripts/ci/common/install-git-crypt.sh 88 | - run: ./scripts/ci/common/install-gpg-key.sh 89 | configure_release_tools: 90 | steps: 91 | - run: ./scripts/ci/common/configure-git.sh 92 | - run: ./scripts/ci/common/configure-clojars.sh 93 | 94 | jobs: 95 | build: 96 | <<: *build_containers 97 | steps: 98 | - checkout 99 | - configure_build_tools 100 | - run: ./scripts/ci/steps/build.sh 101 | - notify 102 | 103 | test: 104 | <<: *test_containers 105 | working_directory: ~/source 106 | steps: 107 | - checkout 108 | - configure_build_tools 109 | - run: ./scripts/ci/steps/test.sh 110 | - notify 111 | 112 | prerelease: 113 | <<: *build_containers 114 | steps: 115 | - checkout 116 | - configure_build_tools 117 | - configure_secrets_tools 118 | - configure_release_tools 119 | - run: ./scripts/ci/steps/prerelease.sh 120 | - notify 121 | 122 | release: 123 | <<: *build_containers 124 | steps: 125 | - checkout 126 | - configure_build_tools 127 | - configure_secrets_tools 128 | - configure_release_tools 129 | - run: ./scripts/ci/steps/release.sh 130 | - notify 131 | 132 | merge_pull_request: 133 | <<: *build_containers 134 | steps: 135 | - checkout 136 | - configure_build_tools 137 | - configure_secrets_tools 138 | - run: ./scripts/ci/steps/merge-pull-request.sh 139 | - notify 140 | 141 | workflows: 142 | version: 2 143 | pipeline: 144 | jobs: 145 | - build: 146 | <<: *only_main_and_dependabot 147 | <<: *slack_context 148 | - test: 149 | <<: *only_main_and_dependabot 150 | <<: *slack_context 151 | requires: 152 | - build 153 | - merge_pull_request: 154 | <<: *only_dependabot 155 | <<: *slack_context 156 | requires: 157 | - test 158 | - prerelease: 159 | <<: *only_main 160 | <<: *slack_context 161 | requires: 162 | - test 163 | - slack/on-hold: 164 | <<: *only_main 165 | <<: *slack_context 166 | requires: 167 | - prerelease 168 | channel: release 169 | template: SLACK_ON_HOLD_NOTIFICATION 170 | - hold: 171 | <<: *only_main 172 | type: approval 173 | requires: 174 | - prerelease 175 | - slack/on-hold 176 | - release: 177 | <<: *only_main 178 | <<: *slack_context 179 | requires: 180 | - hold 181 | -------------------------------------------------------------------------------- /.circleci/gpg.private.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/.circleci/gpg.private.enc -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PROJECT_DIR="$(pwd)" 4 | 5 | PATH_add "${PROJECT_DIR}" 6 | PATH_add "${PROJECT_DIR}"/vendor/**/bin 7 | 8 | if has asdf; then 9 | asdf install 10 | fi 11 | 12 | layout ruby 13 | -------------------------------------------------------------------------------- /.git-crypt/.gitattributes: -------------------------------------------------------------------------------- 1 | # Do not edit this file. To specify the files to encrypt, create your own 2 | # .gitattributes file in the directory where your files are. 3 | * !filter !diff 4 | *.gpg binary 5 | -------------------------------------------------------------------------------- /.git-crypt/keys/default/0/41D2606F66C3FF28874362B61A16916844CE9D82.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/.git-crypt/keys/default/0/41D2606F66C3FF28874362B61A16916844CE9D82.gpg -------------------------------------------------------------------------------- /.git-crypt/keys/default/0/933E3994686DC15C99D1369844037399AEDB1D8D.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/.git-crypt/keys/default/0/933E3994686DC15C99D1369844037399AEDB1D8D.gpg -------------------------------------------------------------------------------- /.git-crypt/keys/default/0/CBDE053F80475F8C49467ED687CE3CAE7DB712F2.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/.git-crypt/keys/default/0/CBDE053F80475F8C49467ED687CE3CAE7DB712F2.gpg -------------------------------------------------------------------------------- /.git-crypt/keys/default/0/D164A61C69E23C0F74475FBE5FFE76AD095FCA07.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/.git-crypt/keys/default/0/D164A61C69E23C0F74475FBE5FFE76AD095FCA07.gpg -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | config/secrets/** filter=git-crypt diff=git-crypt 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "bundler" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vim 2 | *.swp 3 | *.swo 4 | 5 | # IntelliJ 6 | .idea/ 7 | *.ipr 8 | *.iml 9 | *.iws 10 | 11 | # Build 12 | vendor/ 13 | build/ 14 | dist/ 15 | target 16 | **/target 17 | classes 18 | **/classes 19 | checkouts 20 | **/checkouts 21 | run/ 22 | .bundle 23 | .rakeTasks 24 | .direnv 25 | 26 | # OS 27 | .DS_Store 28 | 29 | # Clojure 30 | profiles.clj 31 | .eastwood 32 | pom.xml 33 | pom.xml.asc 34 | *.jar 35 | *.class 36 | .lein-*/ 37 | .lein-failures 38 | lsp/ 39 | .nrepl-port 40 | 41 | # Temporary 42 | run/pids/ 43 | run/logs/ 44 | *.log 45 | .tmp 46 | 47 | -------------------------------------------------------------------------------- /.java-version: -------------------------------------------------------------------------------- 1 | 11.0 2 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - rubocop-rake 3 | 4 | AllCops: 5 | NewCops: enable 6 | 7 | Layout/LineLength: 8 | Max: 80 9 | 10 | Layout/MultilineMethodCallIndentation: 11 | EnforcedStyle: indented_relative_to_receiver 12 | 13 | Metrics/BlockLength: 14 | Enabled: false 15 | 16 | Style/Documentation: 17 | Enabled: false 18 | 19 | Style/SymbolProc: 20 | Enabled: false 21 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.1.1 2 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | ruby 3.1.1 2 | java temurin-11.0.20+101 3 | -------------------------------------------------------------------------------- /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](http://keepachangelog.com) 6 | and this project adheres 7 | to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 8 | 9 | ## [Unreleased] 10 | 11 | ## [0.1.8] — 2023-06-18 12 | 13 | ### Added 14 | 15 | - Further documentation on included check function modules. 16 | - A function to retrieve check name from check. 17 | - A function to retrieve result status from result. 18 | 19 | ## Changed 20 | 21 | - Upgrade to latest versions of all dependencies. 22 | 23 | ## [0.1.7] — 2021-10-30 24 | 25 | ### Added 26 | 27 | - A data source check function module. 28 | - An HTTP endpoint check function module. 29 | - Publishing of an aggregate jar of all module jars as `io.logicblocks/salutem`. 30 | 31 | ### Fixed 32 | 33 | - Use correct SCM details in sub module poms. 34 | 35 | ### Changed 36 | 37 | - Change wording of multiple sections in Getting Started guide. 38 | 39 | ## [0.1.6] — 2021-09-24 40 | 41 | ### Changed 42 | 43 | - Update Getting Started guide with details of asynchronous resolution. 44 | 45 | ## [0.1.5] — 2021-09-23 46 | 47 | ### Added 48 | 49 | - An asynchronous variant of resolve-check. 50 | - An asynchronous version of resolve-checks. 51 | - Include evaluation duration in results. 52 | - Documentation for checks/attempt. 53 | - Documentation to maintenance processes. 54 | 55 | ### Changed 56 | 57 | - Parallelise resolve-checks. 58 | - Namespace salutem specific entries in checks and results. 59 | - Update documentation and expose more signatures in core namespace. 60 | 61 | ## [0.1.4] — 2021-09-17 62 | 63 | ### Changed 64 | 65 | - Update Getting Started guide with list of events logged by maintenance 66 | pipeline. 67 | 68 | ## [0.1.3] — 2021-09-17 69 | 70 | ### Added 71 | 72 | - Create first pass of Getting Started guide. 73 | - Allow asynchronous evaluation of checks. 74 | - Tests for salutem.checks/attempt. 75 | - Catch exceptions from check functions and convert to unhealthy result. 76 | - Ensure only one evaluation of each check takes place at a time in maintenance 77 | pipeline. 78 | - Add performance tests of the maintenance pipeline and registry. 79 | 80 | ### Changed 81 | 82 | - Rename `:ttl` to `:time-to-re-evaluation` to be more clear on what it is used 83 | for. `:ttl` is still supported but consumers should adopt 84 | `:time-to-re-evaluation` instead as `:ttl` may be removed in a future release. 85 | 86 | ## [0.1.2] — Never released 87 | 88 | ## [0.1.1] — 2021-08-26 89 | 90 | ### Fixed 91 | 92 | - Fix issue where `cartus.null` was incorrectly included with test scope 93 | causing consumers to have to install manually for `salutem` to work correctly. 94 | 95 | ## [0.1.0] — 2021-08-24 96 | 97 | ### Added 98 | 99 | - Create _CHANGELOG.md_. 100 | 101 | ## 0.1.0-RC19 — 2021-08-24 102 | 103 | Released without _CHANGELOG.md_. 104 | 105 | 106 | [0.1.0]: https://github.com/logicblocks/salutem/compare/0.1.0-RC19...0.1.0 107 | 108 | [0.1.1]: https://github.com/logicblocks/salutem/compare/0.1.0...0.1.1 109 | 110 | [0.1.3]: https://github.com/logicblocks/salutem/compare/0.1.1...0.1.3 111 | 112 | [0.1.4]: https://github.com/logicblocks/salutem/compare/0.1.3...0.1.4 113 | 114 | [0.1.5]: https://github.com/logicblocks/salutem/compare/0.1.4...0.1.5 115 | 116 | [0.1.6]: https://github.com/logicblocks/salutem/compare/0.1.5...0.1.6 117 | 118 | [0.1.7]: https://github.com/logicblocks/salutem/compare/0.1.6...0.1.7 119 | 120 | [0.1.8]: https://github.com/logicblocks/salutem/compare/0.1.7...0.1.8 121 | [Unreleased]: https://github.com/logicblocks/salutem/compare/0.1.8...HEAD 122 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and 9 | expression, level of experience, education, socio-economic status, nationality, 10 | personal appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at maintainers@logicblocks.io. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an 62 | incident. Further details of specific enforcement policies may be posted 63 | separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 72 | version 1.4, available at 73 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 74 | 75 | [homepage]: https://www.contributor-covenant.org 76 | 77 | For answers to common questions about this code of conduct, see 78 | https://www.contributor-covenant.org/faq 79 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gem 'confidante' 6 | gem 'rake' 7 | gem 'rake_circle_ci' 8 | gem 'rake_docker', '~> 2.22.0.pre.3' 9 | gem 'rake_git' 10 | gem 'rake_git_crypt' 11 | gem 'rake_github' 12 | gem 'rake_gpg' 13 | gem 'rake_leiningen' 14 | gem 'rake_ssh' 15 | gem 'rubocop' 16 | gem 'rubocop-rake' 17 | gem 'rubocop-rspec' 18 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (7.2.1) 5 | base64 6 | bigdecimal 7 | concurrent-ruby (~> 1.0, >= 1.3.1) 8 | connection_pool (>= 2.2.5) 9 | drb 10 | i18n (>= 1.6, < 2) 11 | logger (>= 1.4.2) 12 | minitest (>= 5.1) 13 | securerandom (>= 0.3) 14 | tzinfo (~> 2.0, >= 2.0.5) 15 | addressable (2.8.7) 16 | public_suffix (>= 2.0.2, < 7.0) 17 | ast (2.4.2) 18 | aws-eventstream (1.3.0) 19 | aws-partitions (1.968.0) 20 | aws-sdk-core (3.202.0) 21 | aws-eventstream (~> 1, >= 1.3.0) 22 | aws-partitions (~> 1, >= 1.651.0) 23 | aws-sigv4 (~> 1.9) 24 | jmespath (~> 1, >= 1.6.1) 25 | aws-sdk-ecr (1.80.0) 26 | aws-sdk-core (~> 3, >= 3.201.0) 27 | aws-sigv4 (~> 1.5) 28 | aws-sigv4 (1.9.1) 29 | aws-eventstream (~> 1, >= 1.0.2) 30 | base64 (0.2.0) 31 | bigdecimal (3.1.8) 32 | childprocess (5.0.0) 33 | colored2 (3.1.2) 34 | concurrent-ruby (1.3.4) 35 | confidante (0.28.0) 36 | activesupport (>= 4) 37 | hiera (~> 3.3) 38 | shikashi (~> 0.6) 39 | vault (~> 0.17) 40 | connection_pool (2.4.1) 41 | docker-api (2.3.0) 42 | excon (>= 0.64.0) 43 | multi_json 44 | down (5.4.1) 45 | addressable (~> 2.8) 46 | drb (2.2.1) 47 | evalhook (0.6.0) 48 | partialruby (~> 0.3) 49 | sexp_processor (~> 4.0) 50 | excon (0.111.0) 51 | faraday (2.11.0) 52 | faraday-net_http (>= 2.0, < 3.4) 53 | logger 54 | faraday-net_http (3.3.0) 55 | net-http 56 | getsource (0.2.2) 57 | git (1.18.0) 58 | addressable (~> 2.8) 59 | rchardet (~> 1.8) 60 | hamster (3.0.0) 61 | concurrent-ruby (~> 1.0) 62 | hiera (3.12.0) 63 | i18n (1.14.5) 64 | concurrent-ruby (~> 1.0) 65 | immutable-struct (2.4.1) 66 | jmespath (1.6.2) 67 | json (2.9.1) 68 | language_server-protocol (3.17.0.3) 69 | lino (4.1.0) 70 | childprocess (~> 5.0.0) 71 | hamster (~> 3.0) 72 | open4 (~> 1.3) 73 | logger (1.6.0) 74 | minitar (0.9) 75 | minitest (5.25.1) 76 | multi_json (1.15.0) 77 | net-http (0.4.1) 78 | uri 79 | octokit (8.1.0) 80 | base64 81 | faraday (>= 1, < 3) 82 | sawyer (~> 0.9) 83 | open4 (1.3.4) 84 | parallel (1.26.3) 85 | parser (3.3.7.0) 86 | ast (~> 2.4.1) 87 | racc 88 | partialruby (0.3.0) 89 | ruby2ruby (~> 2) 90 | ruby_parser (~> 3) 91 | public_suffix (6.0.1) 92 | racc (1.8.1) 93 | rainbow (3.1.1) 94 | rake (13.2.1) 95 | rake_circle_ci (0.13.0) 96 | colored2 (~> 3.1) 97 | excon (~> 0.72) 98 | rake_factory (~> 0.33) 99 | sshkey (~> 2.0) 100 | rake_dependencies (3.9.0) 101 | down (~> 5.3) 102 | hamster (~> 3.0) 103 | minitar (~> 0.9) 104 | rake_factory (~> 0.33) 105 | rubyzip (>= 1.3) 106 | rake_docker (2.22.0.pre.3) 107 | aws-sdk-ecr (~> 1.2) 108 | colored2 (~> 3.1) 109 | docker-api (>= 2.3, < 3.0) 110 | rake_factory (~> 0.33) 111 | rake_factory (0.34.0.pre.2) 112 | activesupport (>= 4) 113 | rake (~> 13.0) 114 | rake_git (0.3.0.pre.2) 115 | colored2 (~> 3.1) 116 | git (~> 1.13, >= 1.13.2) 117 | rake_factory (~> 0.33) 118 | rake_git_crypt (0.3.0.pre.2) 119 | colored2 (~> 3.1) 120 | rake_factory (~> 0.33) 121 | ruby_git_crypt (~> 0.1) 122 | ruby_gpg2 (~> 0.12) 123 | rake_github (0.15.0) 124 | colored2 (~> 3.1) 125 | octokit (>= 4.16, < 9.0) 126 | rake_factory (~> 0.33) 127 | sshkey (~> 2.0) 128 | rake_gpg (0.20.0) 129 | rake_factory (~> 0.33) 130 | ruby_gpg2 (~> 0.12) 131 | rake_leiningen (0.38.0) 132 | rake_dependencies (~> 3.7) 133 | rake_factory (~> 0.33) 134 | ruby_leiningen (~> 0.29) 135 | semantic (~> 1.6) 136 | rake_ssh (0.12.0) 137 | colored2 (~> 3.1) 138 | rake_factory (~> 0.33) 139 | sshkey (~> 2.0) 140 | rchardet (1.8.0) 141 | regexp_parser (2.10.0) 142 | rexml (3.4.0) 143 | rubocop (1.65.1) 144 | json (~> 2.3) 145 | language_server-protocol (>= 3.17.0) 146 | parallel (~> 1.10) 147 | parser (>= 3.3.0.2) 148 | rainbow (>= 2.2.2, < 4.0) 149 | regexp_parser (>= 2.4, < 3.0) 150 | rexml (>= 3.2.5, < 4.0) 151 | rubocop-ast (>= 1.31.1, < 2.0) 152 | ruby-progressbar (~> 1.7) 153 | unicode-display_width (>= 2.4.0, < 3.0) 154 | rubocop-ast (1.37.0) 155 | parser (>= 3.3.1.0) 156 | rubocop-rake (0.6.0) 157 | rubocop (~> 1.0) 158 | rubocop-rspec (3.4.0) 159 | rubocop (~> 1.61) 160 | ruby-progressbar (1.13.0) 161 | ruby2ruby (2.5.0) 162 | ruby_parser (~> 3.1) 163 | sexp_processor (~> 4.6) 164 | ruby_git_crypt (0.2.0.pre.2) 165 | immutable-struct (~> 2.4) 166 | lino (>= 4.1) 167 | ruby_gpg2 (0.12.0) 168 | lino (>= 4.1) 169 | ruby_leiningen (0.29.0) 170 | activesupport (>= 6.0.2, < 8) 171 | lino (>= 4.1) 172 | ruby_parser (3.20.3) 173 | sexp_processor (~> 4.16) 174 | rubyzip (2.3.2) 175 | sawyer (0.9.2) 176 | addressable (>= 2.3.5) 177 | faraday (>= 0.17.3, < 3) 178 | securerandom (0.3.1) 179 | semantic (1.6.1) 180 | sexp_processor (4.17.0) 181 | shikashi (0.6.0) 182 | evalhook (>= 0.6.0) 183 | getsource (>= 0.1.0) 184 | sshkey (2.0.0) 185 | tzinfo (2.0.6) 186 | concurrent-ruby (~> 1.0) 187 | unicode-display_width (2.6.0) 188 | uri (0.13.1) 189 | vault (0.18.2) 190 | aws-sigv4 191 | 192 | PLATFORMS 193 | arm64-darwin-21 194 | arm64-darwin-22 195 | ruby 196 | x86_64-linux 197 | 198 | DEPENDENCIES 199 | confidante 200 | rake 201 | rake_circle_ci 202 | rake_docker (~> 2.22.0.pre.3) 203 | rake_git 204 | rake_git_crypt 205 | rake_github 206 | rake_gpg 207 | rake_leiningen 208 | rake_ssh 209 | rubocop 210 | rubocop-rake 211 | rubocop-rspec 212 | 213 | BUNDLED WITH 214 | 2.2.25 215 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © 2023 LogicBlocks Maintainers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # salutem 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/io.logicblocks/salutem.core.svg)](https://clojars.org/io.logicblocks/salutem.core) 4 | [![Clojars Downloads](https://img.shields.io/clojars/dt/io.logicblocks/salutem.core.svg)](https://clojars.org/io.logicblocks/salutem.core) 5 | [![GitHub Contributors](https://img.shields.io/github/contributors-anon/logicblocks/salutem.svg)](https://github.com/logicblocks/salutem/graphs/contributors) 6 | 7 | A system for defining and maintaining a collection of health checks. 8 | 9 | `salutem` supports: 10 | * both realtime and background checks 11 | * a registry for storing, finding and resolving checks 12 | * an asynchronous maintenance system for ensuring that the results of checks are 13 | kept up-to-date; and 14 | * notifying via callbacks after checks are evaluated. 15 | 16 | `salutem` also provides check function implementations for: 17 | 18 | * data sources; and 19 | * HTTP endpoints. 20 | 21 | ## Install 22 | 23 | Add the following to your `project.clj` file: 24 | 25 | ```clojure 26 | [io.logicblocks/salutem.core "0.1.8"] 27 | ``` 28 | 29 | ## Documentation 30 | 31 | * [API Docs](http://logicblocks.github.io/salutem) 32 | * [Getting Started](https://logicblocks.github.io/salutem/01-getting-started.html) 33 | 34 | ## Usage 35 | 36 | ```clojure 37 | (require '[salutem.core :as salutem]) 38 | 39 | (defn database-health-check-fn 40 | [context callback-fn] 41 | (callback-fn 42 | (salutem/unhealthy 43 | {:error :connection-failed}))) 44 | 45 | (defn external-service-health-check-fn 46 | [context callback-fn] 47 | (callback-fn 48 | (salutem/healthy 49 | {:latency-ms 200}))) 50 | 51 | (def registry-atom 52 | (atom 53 | (-> (salutem/empty-registry) 54 | (salutem/with-check 55 | (salutem/realtime-check :database 56 | database-health-check-fn 57 | {:salutem/timeout (salutem/duration 5 :seconds)})) 58 | (salutem/with-check 59 | (salutem/background-check :external-service 60 | external-service-health-check-fn 61 | {:salutem/time-to-re-evaluation (salutem/duration 30 :seconds)}))))) 62 | 63 | (def maintainer 64 | (salutem/maintain registry-atom)) 65 | 66 | (salutem/resolve-checks @registry-atom) 67 | ; => {:database 68 | ; {:error :connection-failed 69 | ; :salutem/status :unhealthy 70 | ; :salutem/evaluated-at #time/instant"2021-08-18T23:39:29.234Z"} 71 | ; :external-service 72 | ; {:latency-ms 200, 73 | ; :salutem/status :healthy, 74 | ; :salutem/evaluated-at #time/instant"2021-08-18T23:39:10.383Z"}} 75 | 76 | ; ...5 seconds later... 77 | 78 | (salutem/resolve-checks @registry-atom) 79 | ; => {:database 80 | ; {:error :connection-failed 81 | ; :salutem/status :unhealthy 82 | ; :salutem/evaluated-at #time/instant"2021-08-18T23:39:34.234Z"} 83 | ; :external-service 84 | ; {:latency-ms 200, 85 | ; :salutem/status :healthy, 86 | ; :salutem/evaluated-at #time/instant"2021-08-18T23:39:10.383Z"}} 87 | 88 | (salutem/shutdown maintainer) 89 | ``` 90 | 91 | ## License 92 | 93 | Copyright © 2023 LogicBlocks Maintainers 94 | 95 | Distributed under the terms of the 96 | [MIT License](http://opensource.org/licenses/MIT). 97 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'confidante' 4 | require 'rake_circle_ci' 5 | require 'rake_docker' 6 | require 'rake_git' 7 | require 'rake_git_crypt' 8 | require 'rake_github' 9 | require 'rake_gpg' 10 | require 'rake_leiningen' 11 | require 'rake_ssh' 12 | require 'rubocop/rake_task' 13 | require 'yaml' 14 | 15 | task default: %i[ 16 | build:code:fix 17 | library:initialise 18 | library:check 19 | library:test:all 20 | ] 21 | 22 | RubyLeiningen::Commands.define_custom_command('install') 23 | 24 | RakeLeiningen.define_installation_tasks( 25 | version: '2.10.0' 26 | ) 27 | 28 | RakeGitCrypt.define_standard_tasks( 29 | namespace: :git_crypt, 30 | 31 | provision_secrets_task_name: :'secrets:provision', 32 | destroy_secrets_task_name: :'secrets:destroy', 33 | 34 | install_commit_task_name: :'git:commit', 35 | uninstall_commit_task_name: :'git:commit', 36 | 37 | gpg_user_key_paths: %w[ 38 | config/gpg 39 | config/secrets/ci/gpg.public 40 | ] 41 | ) 42 | 43 | namespace :git do 44 | RakeGit.define_commit_task( 45 | argument_names: [:message] 46 | ) do |t, args| 47 | t.message = args.message 48 | end 49 | end 50 | 51 | namespace :encryption do 52 | namespace :directory do 53 | desc 'Ensure CI secrets directory exists.' 54 | task :ensure do 55 | FileUtils.mkdir_p('config/secrets/ci') 56 | end 57 | end 58 | 59 | namespace :passphrase do 60 | desc 'Generate encryption passphrase for CI GPG key' 61 | task generate: ['directory:ensure'] do 62 | File.write( 63 | 'config/secrets/ci/encryption.passphrase', 64 | SecureRandom.base64(36) 65 | ) 66 | end 67 | end 68 | end 69 | 70 | namespace :keys do 71 | namespace :deploy do 72 | RakeSSH.define_key_tasks( 73 | path: 'config/secrets/ci/', 74 | comment: 'maintainers@logicblocks.io' 75 | ) 76 | end 77 | 78 | namespace :secrets do 79 | namespace :gpg do 80 | RakeGPG.define_generate_key_task( 81 | output_directory: 'config/secrets/ci', 82 | name_prefix: 'gpg', 83 | owner_name: 'LogicBlocks Maintainers', 84 | owner_email: 'maintainers@logicblocks.io', 85 | owner_comment: 'salutem CI Key' 86 | ) 87 | end 88 | 89 | task generate: ['gpg:generate'] 90 | end 91 | end 92 | 93 | namespace :secrets do 94 | namespace :directory do 95 | desc 'Ensure secrets directory exists and is set up correctly' 96 | task :ensure do 97 | FileUtils.mkdir_p('config/secrets') 98 | unless File.exist?('config/secrets/.unlocked') 99 | File.write('config/secrets/.unlocked', 100 | 'true') 101 | end 102 | end 103 | end 104 | 105 | desc 'Generate all generatable secrets.' 106 | task regenerate: %w[ 107 | encryption:passphrase:generate 108 | keys:deploy:generate 109 | keys:secrets:generate 110 | ] 111 | 112 | desc 'Provision all secrets.' 113 | task provision: [:regenerate] 114 | 115 | desc 'Delete all secrets.' 116 | task :destroy do 117 | rm_rf 'config/secrets' 118 | end 119 | 120 | desc 'Rotate all secrets.' 121 | task rotate: [:'git_crypt:reinstall'] 122 | end 123 | 124 | RakeCircleCI.define_project_tasks( 125 | namespace: :circle_ci, 126 | project_slug: 'github/logicblocks/salutem' 127 | ) do |t| 128 | circle_ci_config = 129 | YAML.load_file('config/secrets/circle_ci/config.yaml') 130 | 131 | t.api_token = circle_ci_config['circle_ci_api_token'] 132 | t.environment_variables = { 133 | ENCRYPTION_PASSPHRASE: 134 | File.read('config/secrets/ci/encryption.passphrase') 135 | .chomp 136 | } 137 | t.checkout_keys = [] 138 | t.ssh_keys = [ 139 | { 140 | hostname: 'github.com', 141 | private_key: File.read('config/secrets/ci/ssh.private') 142 | } 143 | ] 144 | end 145 | 146 | RakeGithub.define_repository_tasks( 147 | namespace: :github, 148 | repository: 'logicblocks/salutem' 149 | ) do |t| 150 | github_config = 151 | YAML.load_file('config/secrets/github/config.yaml') 152 | 153 | t.access_token = github_config['github_personal_access_token'] 154 | t.deploy_keys = [ 155 | { 156 | title: 'CircleCI', 157 | public_key: File.read('config/secrets/ci/ssh.public') 158 | } 159 | ] 160 | end 161 | 162 | namespace :pipeline do 163 | desc 'Prepare CircleCI Pipeline' 164 | task prepare: %i[ 165 | circle_ci:env_vars:ensure 166 | circle_ci:checkout_keys:ensure 167 | circle_ci:ssh_keys:ensure 168 | github:deploy_keys:ensure 169 | ] 170 | end 171 | 172 | RuboCop::RakeTask.new 173 | 174 | namespace :build do 175 | namespace :code do 176 | desc 'Run all checks on the test code' 177 | task check: [:rubocop] 178 | 179 | desc 'Attempt to automatically fix issues with the test code' 180 | task fix: [:'rubocop:autocorrect_all'] 181 | end 182 | end 183 | 184 | namespace :library do 185 | desc 'Initialise all modules in the local maven repository' 186 | task initialise: [:'leiningen:ensure'] do 187 | RubyLeiningen.install 188 | end 189 | 190 | RakeLeiningen.define_check_tasks(fix: true) 191 | 192 | namespace :test do 193 | RakeLeiningen.define_test_task( 194 | name: :unit, 195 | type: 'unit', 196 | profile: 'unit' 197 | ) 198 | 199 | RakeLeiningen.define_test_task( 200 | name: :integration, 201 | type: 'integration', 202 | profile: 'integration' 203 | ) do 204 | Rake::Task['database:test:provision'].invoke unless ENV['CI'] == 'true' 205 | end 206 | 207 | RakeLeiningen.define_test_task( 208 | name: :performance, 209 | type: 'performance', 210 | profile: 'performance' 211 | ) 212 | 213 | task all: %i[unit integration performance] 214 | end 215 | 216 | namespace :publish do 217 | RakeLeiningen.define_release_task( 218 | name: :prerelease, 219 | profile: 'prerelease' 220 | ) 221 | 222 | RakeLeiningen.define_release_task( 223 | name: :release, 224 | profile: 'release' 225 | ) 226 | end 227 | end 228 | 229 | namespace :database do 230 | namespace :test do 231 | RakeDocker.define_container_tasks( 232 | container_name: 'salutem-test-database' 233 | ) do |t| 234 | t.image = 'postgres:11.12' 235 | t.ports = ['5432:5432'] 236 | t.environment = %w[ 237 | POSTGRES_DB=test 238 | POSTGRES_PASSWORD=test-password 239 | POSTGRES_USER=tester 240 | ] 241 | end 242 | end 243 | end 244 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ToDo 2 | ==== 3 | 4 | * Add database check function 5 | * Add service check function 6 | * Add `refresh-results` function to registry namespace and expose 7 | * Consider introducing start-channel to separate creation from execution, and 8 | renaming shutdown channel to stop channel 9 | * Consider a Completer process to capture when a trigger has fully completed 10 | 11 | Open Questions 12 | ============== 13 | 14 | * How should we handle checks that continuously time out? 15 | * This could be the responsibility of the implementer of a check function 16 | * We could also use exponential backoff in the maintenance pipeline 17 | * Might be better to leave this at the discretion of the implementer 18 | * How would we support composite checks, where the result depends on the results 19 | of other checks? 20 | * We could build a dependency tree between checks and then manage evaluation 21 | of those checks based on the dependency tree 22 | * Alternatively, we could introduce a different check type and lazily resolve 23 | those checks at resolution time 24 | * This would likely still require a dependency tree to prevent re-evaluating 25 | realtime checks many times 26 | -------------------------------------------------------------------------------- /assets/diagrams.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/assets/diagrams.graffle -------------------------------------------------------------------------------- /check-fns/data-source/dev/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.java.javadoc :refer [javadoc]] 5 | [clojure.pprint :refer [pprint]] 6 | [clojure.reflect :refer [reflect]] 7 | [clojure.repl :refer [apropos dir doc find-doc pst source]] 8 | [clojure.set :as set] 9 | [clojure.string :as string] 10 | [clojure.tools.namespace.repl :refer [refresh refresh-all clear]] 11 | 12 | [eftest.runner :refer [find-tests run-tests]])) 13 | 14 | (defn run-tests-in [& dirs] 15 | (run-tests 16 | (find-tests dirs) 17 | {:multithread? false})) 18 | -------------------------------------------------------------------------------- /check-fns/data-source/project.clj: -------------------------------------------------------------------------------- 1 | (defproject io.logicblocks/salutem.check-fns.data-source "0.1.9-RC4" 2 | :description "A data source check function for salutem." 3 | 4 | :parent-project {:path "../../parent/project.clj" 5 | :inherit [:scm 6 | :url 7 | :license 8 | :plugins 9 | [:profiles :parent-shared] 10 | [:profiles :parent-reveal] 11 | [:profiles :parent-dev] 12 | [:profiles :parent-unit] 13 | [:profiles :parent-integration] 14 | :deploy-repositories 15 | :managed-dependencies 16 | :cloverage 17 | :bikeshed 18 | :cljfmt 19 | :eastwood]} 20 | 21 | :plugins [[lein-parent "0.3.9"]] 22 | 23 | :dependencies [[io.logicblocks/salutem.core] 24 | 25 | [io.logicblocks/cartus.core] 26 | [io.logicblocks/cartus.null] 27 | 28 | [tick] 29 | 30 | [com.github.seancorfield/next.jdbc]] 31 | 32 | :profiles 33 | {:shared ^{:pom-scope :test} 34 | {:dependencies [[org.jooq/jooq] 35 | [com.impossibl.pgjdbc-ng/pgjdbc-ng]]} 36 | 37 | :dev [:parent-dev :shared] 38 | 39 | :reveal [:parent-reveal] 40 | 41 | :unit [:parent-unit :shared 42 | {:eftest {:multithread? false}}] 43 | 44 | :integration [:parent-integration :shared 45 | {:eftest {:multithread? false}}]} 46 | 47 | :test-paths ["test/shared" 48 | "test/unit" 49 | "test/integration"] 50 | :resource-paths []) 51 | -------------------------------------------------------------------------------- /check-fns/data-source/src/salutem/check_fns/data_source/core.clj: -------------------------------------------------------------------------------- 1 | (ns salutem.check-fns.data-source.core 2 | "Provides a data source check function for salutem. 3 | 4 | Packaged in a separate module, `salutem.check-fns.data-source` versioned 5 | in lock step with `salutem.core`." 6 | (:require 7 | [next.jdbc :as jdbc] 8 | [next.jdbc.result-set :as jdbc-rs] 9 | 10 | [tick.core :as time] 11 | 12 | [cartus.core :as log] 13 | [cartus.null :as cn] 14 | 15 | [salutem.core :as salutem]) 16 | (:import 17 | [java.sql SQLTimeoutException])) 18 | 19 | (defn- resolve-if-fn [thing context] 20 | (if (fn? thing) (thing context) thing)) 21 | 22 | (defn failure-reason 23 | "Determines the failure reason associated with an exception. 24 | 25 | This failure reason function, the default used by the check function, uses a 26 | reason of `:threw-exception` for all exceptions other than a 27 | [java.sql.SQLTimeoutException](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/SQLTimeoutException.html) 28 | for which the reason is `:timed-out`. 29 | 30 | In the case that this default behaviour is insufficient, an alternative 31 | failure reason function can be passed to [[data-source-check-fn]] using the 32 | `:failure-reason-fn` option." 33 | [exception] 34 | (if (= (class exception) SQLTimeoutException) 35 | :timed-out 36 | :threw-exception)) 37 | 38 | (defn data-source-check-fn 39 | "Returns a check function for the provided 40 | [javax.sql.DataSource](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/javax/sql/DataSource.html). 41 | 42 | Accepts the following options in the option map: 43 | 44 | - `:query-sql-params`: an SQL parameter vector (as defined in 45 | [`next.jdbc`](https://github.com/seancorfield/next-jdbc)) or a function 46 | of context that will return an SQL parameter vector containing the 47 | health check query to execute against the data source, defaults to 48 | `[\"SELECT 1 AS up;\"]`. 49 | - `:query-timeout`: a [[salutem.core/duration]] or a function of context 50 | that will return a [[salutem.core/duration]] representing the amount of 51 | time to wait for the query to finish before considering it failed; 52 | defaults to 5 seconds. 53 | - `:query-opts`: additional options (as defined in 54 | [`next.jdbc`](https://github.com/seancorfield/next-jdbc)) or a function 55 | of context that will return additional options to pass at query execution 56 | time; by default, includes a builder function for rows that returns 57 | unqualified kebab-cased maps; additional options are merged into the 58 | default option map. 59 | - `:query-results-result-fn`: a function, of context and the results of the 60 | query, used to produce a result for the check; by default, a healthy 61 | result is returned, including the contents of the first record from the 62 | results. 63 | - `:failure-reason-fn`: a function, of context and an exception, to 64 | determine the reason for a failure; by default uses [[failure-reason]]. 65 | - `:exception-result-fn`: a function, of context and an exception, used to 66 | produce a result for the check in the case that an exception is thrown; 67 | by default, an unhealthy result is returned including a `:salutem/reason` 68 | entry with the reason derived by `:failure-reason-fn` and a 69 | `:salutem/exception` entry containing the thrown exception. 70 | 71 | If the returned check function is invoked with a context map including a 72 | `:logger` key with a 73 | [`cartus.core/Logger`](https://logicblocks.github.io/cartus/cartus.core.html#var-Logger) 74 | value, the check function will emit a number of log events whilst 75 | executing." 76 | ([data-source] (data-source-check-fn data-source {})) 77 | ([data-source 78 | {:keys [query-sql-params 79 | query-timeout 80 | query-opts 81 | query-results-result-fn 82 | failure-reason-fn 83 | exception-result-fn]}] 84 | ; Defaulting this way keeps argument documentation more readable while 85 | ; allowing defaults to be viewed more clearly using 'view source'. 86 | (let [query-sql-params 87 | (or query-sql-params ["SELECT 1 AS up;"]) 88 | 89 | query-timeout 90 | (or query-timeout (time/new-duration 5 :seconds)) 91 | 92 | query-results-result-fn 93 | (or query-results-result-fn 94 | (fn [_ results] 95 | (salutem/healthy (first results)))) 96 | 97 | failure-reason-fn 98 | (or failure-reason-fn 99 | (fn [_ exception] 100 | (failure-reason exception))) 101 | 102 | exception-result-fn 103 | (or exception-result-fn 104 | (fn [context exception] 105 | (salutem/unhealthy 106 | {:salutem/reason (failure-reason-fn context exception) 107 | :salutem/exception exception})))] 108 | (fn [context result-cb] 109 | (let [logger (get context :logger (cn/logger))] 110 | (future 111 | (try 112 | (let [query-sql-params (resolve-if-fn query-sql-params context) 113 | query-timeout (resolve-if-fn query-timeout context) 114 | query-opts 115 | (merge 116 | {:builder-fn jdbc-rs/as-unqualified-kebab-maps} 117 | (resolve-if-fn query-opts context) 118 | {:timeout (time/seconds query-timeout)})] 119 | (log/info logger 120 | :salutem.check-fns.data-source/check.starting 121 | {:query-sql-params query-sql-params}) 122 | (let [results 123 | (jdbc/execute! data-source query-sql-params query-opts)] 124 | (log/info logger 125 | :salutem.check-fns.data-source/check.successful) 126 | (result-cb 127 | (query-results-result-fn context results)))) 128 | (catch Exception exception 129 | (log/warn logger 130 | :salutem.check-fns.data-source/check.failed 131 | {:reason (failure-reason-fn context exception)} 132 | {:exception exception}) 133 | (result-cb 134 | (exception-result-fn context exception)))))))))) 135 | -------------------------------------------------------------------------------- /check-fns/data-source/test/integration/salutem/check_fns/data_source/timeout_test.clj: -------------------------------------------------------------------------------- 1 | (ns salutem.check-fns.data-source.timeout-test 2 | (:require 3 | [clojure.test :refer :all] 4 | 5 | [next.jdbc :as jdbc] 6 | 7 | [salutem.core :as salutem] 8 | [salutem.check-fns.data-source.core :as scfds]) 9 | (:import 10 | [com.impossibl.postgres.jdbc PGSQLSimpleException])) 11 | 12 | (def db-spec 13 | {:dbtype "pgsql" 14 | :dbname "test" 15 | :host "localhost" 16 | :port "5432" 17 | :user "tester" 18 | :password "test-password"}) 19 | 20 | (deftest data-source-check-fn-times-out-after-5-seconds-by-default 21 | (let [data-source (jdbc/get-datasource db-spec)] 22 | (let [check-fn (scfds/data-source-check-fn data-source 23 | {:query-sql-params 24 | ["SELECT pg_sleep(10) AS up;"] 25 | :failure-reason-fn 26 | (fn [_ ^Exception exception] 27 | (if (and 28 | (isa? (class exception) PGSQLSimpleException) 29 | (= (.getMessage exception) 30 | "canceling statement due to user request")) 31 | :timed-out 32 | :threw-exception))}) 33 | context {} 34 | result-promise (promise) 35 | result-cb (partial deliver result-promise)] 36 | (check-fn context result-cb) 37 | 38 | (let [result (deref result-promise 7500 nil)] 39 | (is (salutem/unhealthy? result)) 40 | (is (= :timed-out (:salutem/reason result))) 41 | (is (not (nil? (:salutem/exception result)))))))) 42 | 43 | (deftest data-source-check-fn-uses-supplied-timeout-when-specified 44 | (let [data-source (jdbc/get-datasource db-spec)] 45 | (let [check-fn (scfds/data-source-check-fn data-source 46 | {:query-sql-params 47 | ["SELECT pg_sleep(5) AS up;"] 48 | :query-timeout 49 | (salutem/duration 1 :seconds) 50 | :failure-reason-fn 51 | (fn [_ ^Exception exception] 52 | (if (and 53 | (isa? (class exception) PGSQLSimpleException) 54 | (= (.getMessage exception) 55 | "canceling statement due to user request")) 56 | :timed-out 57 | :threw-exception))}) 58 | context {} 59 | result-promise (promise) 60 | result-cb (partial deliver result-promise)] 61 | (check-fn context result-cb) 62 | 63 | (let [result (deref result-promise 3000 nil)] 64 | (is (salutem/unhealthy? result)) 65 | (is (= :timed-out (:salutem/reason result))) 66 | (is (not (nil? (:salutem/exception result)))))))) 67 | -------------------------------------------------------------------------------- /check-fns/data-source/test/shared/salutem/test/support/jdbc.clj: -------------------------------------------------------------------------------- 1 | (ns salutem.test.support.jdbc 2 | (:import 3 | [javax.sql DataSource] 4 | [org.jooq.tools.jdbc MockConnection MockDataProvider MockResult MockExecuteContext] 5 | [org.jooq.impl DSL] 6 | [org.jooq SQLDialect Record Result Field DSLContext] 7 | [java.util Collection])) 8 | 9 | (defn- dsl [] 10 | ^DSLContext (DSL/using SQLDialect/DEFAULT)) 11 | 12 | (defn- field [key value] 13 | ^Field (DSL/field (name key) (class value))) 14 | 15 | (defn- fields [record-map] 16 | ^Collection (map (fn [[key value]] (field key value)) record-map)) 17 | 18 | (defn- record [record-map] 19 | (reduce 20 | (fn [^Record record [key value]] 21 | (.set record (field key value) value) 22 | record) 23 | ^Record (.newRecord 24 | ^DSLContext (dsl) 25 | ^Collection (fields record-map)) 26 | record-map)) 27 | 28 | (defn- result [record-maps] 29 | 30 | (let [records (map record record-maps) 31 | result ^Result (.newResult 32 | ^DSLContext (dsl) 33 | ^Collection (fields (first record-maps)))] 34 | (.addAll result records) 35 | result)) 36 | 37 | (defn- results [result-sets] 38 | (into-array MockResult 39 | (map 40 | (fn [[rows record-maps]] 41 | (MockResult. rows (result record-maps))) 42 | result-sets))) 43 | 44 | (defn- context->map [^MockExecuteContext context] 45 | {:batch? (.batch context) 46 | :batch-single? (.batchSingle context) 47 | :batch-multiple? (.batchMultiple context) 48 | :batch-sql (into [] (.batchSQL context)) 49 | :batch-bindings (into [] (map #(into [] %) (.batchBindings context))) 50 | :sql (.sql context) 51 | :bindings (into [] (.bindings context)) 52 | :auto-generated-keys (.autoGeneratedKeys context) 53 | :column-indexes (into [] (.columnIndexes context)) 54 | :column-names (into [] (.columnNames context)) 55 | :out-parameter-types (into [] (.outParameterTypes context))}) 56 | 57 | (defn mock-connection [data-provider-fn] 58 | (MockConnection. 59 | (reify MockDataProvider 60 | (execute [_ context] 61 | (results (data-provider-fn (context->map context))))))) 62 | 63 | (defn mock-data-source [mock-connection] 64 | (let [login-timeout (atom nil)] 65 | (reify DataSource 66 | (getConnection [_] 67 | mock-connection) 68 | (getConnection [_ _ _] 69 | mock-connection) 70 | (getLoginTimeout [_] (or @login-timeout 0)) 71 | (setLoginTimeout [_ secs] (reset! login-timeout secs))))) 72 | -------------------------------------------------------------------------------- /check-fns/http-endpoint/dev/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.java.javadoc :refer [javadoc]] 5 | [clojure.pprint :refer [pprint]] 6 | [clojure.reflect :refer [reflect]] 7 | [clojure.repl :refer [apropos dir doc find-doc pst source]] 8 | [clojure.set :as set] 9 | [clojure.string :as string] 10 | [clojure.tools.namespace.repl :refer [refresh refresh-all clear]] 11 | 12 | [eftest.runner :refer [find-tests run-tests]])) 13 | 14 | (defn run-tests-in [& dirs] 15 | (run-tests 16 | (find-tests dirs) 17 | {:multithread? false})) 18 | -------------------------------------------------------------------------------- /check-fns/http-endpoint/project.clj: -------------------------------------------------------------------------------- 1 | (defproject io.logicblocks/salutem.check-fns.http-endpoint "0.1.9-RC4" 2 | :description "An HTTP endpoint check function for salutem." 3 | 4 | :parent-project {:path "../../parent/project.clj" 5 | :inherit [:scm 6 | :url 7 | :license 8 | :plugins 9 | [:profiles :parent-shared] 10 | [:profiles :parent-reveal] 11 | [:profiles :parent-dev] 12 | [:profiles :parent-unit] 13 | [:profiles :parent-integration] 14 | :deploy-repositories 15 | :managed-dependencies 16 | :cloverage 17 | :bikeshed 18 | :cljfmt 19 | :eastwood]} 20 | 21 | :plugins [[lein-parent "0.3.9"]] 22 | 23 | :dependencies [[io.logicblocks/salutem.core] 24 | 25 | [io.logicblocks/cartus.core] 26 | [io.logicblocks/cartus.null] 27 | 28 | [tick] 29 | 30 | [clj-http]] 31 | 32 | :profiles 33 | {:shared ^{:pom-scope :test} 34 | {:dependencies [[clj-http-fake] 35 | [kelveden/clj-wiremock] 36 | [org.slf4j/slf4j-nop]]} 37 | 38 | :dev [:parent-dev :shared] 39 | 40 | :reveal [:parent-reveal] 41 | 42 | :unit [:parent-unit :shared 43 | {:eftest {:multithread? false}}] 44 | 45 | :integration [:parent-integration :shared]} 46 | 47 | :test-paths ["test/shared" 48 | "test/unit" 49 | "test/integration"] 50 | :resource-paths []) 51 | -------------------------------------------------------------------------------- /check-fns/http-endpoint/src/salutem/check_fns/http_endpoint/core.clj: -------------------------------------------------------------------------------- 1 | (ns salutem.check-fns.http-endpoint.core 2 | "Provides an HTTP endpoint check function for salutem. 3 | 4 | Packaged in a separate module, `salutem.check-fns.http-endpoint` versioned 5 | in lock step with `salutem.core`." 6 | (:require 7 | [clj-http.client :as http] 8 | 9 | [tick.core :as time] 10 | 11 | [cartus.core :as log] 12 | [cartus.null :as cn] 13 | 14 | [salutem.core :as salutem]) 15 | (:import 16 | [org.apache.http.conn ConnectTimeoutException] 17 | [java.net SocketTimeoutException ConnectException])) 18 | 19 | (defn- resolve-if-fn [thing context] 20 | (if (fn? thing) (thing context) thing)) 21 | 22 | (defn failure-reason 23 | "Determines the failure reason associated with an exception. 24 | 25 | This failure reason function, the default used by the check function, uses a 26 | reason of `:threw-exception` for all exceptions other than: 27 | 28 | * [org.apache.http.conn.ConnectTimeoutException](https://www.javadoc.io/doc/org.apache.httpcomponents/httpclient/latest/org/apache/http/conn/ConnectTimeoutException.html) 29 | * [java.net.SocketTimeoutException](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/SQLTimeoutException.html) 30 | * [java.net.ConnectException](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/SQLTimeoutException.html) 31 | when the exception message includes \"Timeout\" 32 | 33 | for which the reason is `:timed-out`. 34 | 35 | In the case that this default behaviour is insufficient, an alternative 36 | failure reason function can be passed to [[http-endpoint-check-fn]] using the 37 | `:failure-reason-fn` option." 38 | [exception] 39 | (let [exception-class (class exception) 40 | exception-message (ex-message exception) 41 | contains-timeout (re-matches #".*Timeout.*" exception-message)] 42 | (if (or 43 | (isa? exception-class ConnectTimeoutException) 44 | (isa? exception-class SocketTimeoutException) 45 | (and (isa? exception-class ConnectException) contains-timeout)) 46 | :timed-out 47 | :threw-exception))) 48 | 49 | (defn successful? 50 | "Returns true if the provided response has a successful status, false 51 | otherwise. 52 | 53 | This response success function, the default used by the check function, 54 | treats status codes of 200, 201, 202, 203, 204, 205, 206, 207, 300, 301, 302, 55 | 303, 304, 307 and 308 as successful. 56 | 57 | In the case that this default behaviour is insufficient, an alternative 58 | response success function can be passed to [[http-endpoint-check-fn]] using 59 | the `:successful-response-fn` option." 60 | [{:keys [status]}] 61 | (http/unexceptional-status? status)) 62 | 63 | (defn http-endpoint-check-fn 64 | "Returns a check function for the HTTP endpoint identified by the provided 65 | URL. 66 | 67 | Accepts the following options in the option map: 68 | 69 | - `:method`: a keyword representing the method used to check the endpoint 70 | (one of `:get`, `:head`, `:post`, `:put`, `:delete`, `:options`, `:copy`, 71 | `:move` or `:patch`) or a function of context that will return such a 72 | keyword; defaults to `:get`. 73 | - `:body`: an object representing the body sent to the endpoint on check 74 | execution (supporting anything [`clj-http`](https://github.com/dakrone/clj-http) 75 | will accept) or a function of context that will return such an object; 76 | defaults to `nil`. 77 | - `:headers`: a map of headers to be sent to the endpoint on check 78 | execution (as supported by [`clj-http`](https://github.com/dakrone/clj-http)) 79 | or a function of context that will return such a map; defaults to 80 | `nil`. 81 | - `:query-params`: a map of query parameters to be sent to the endpoint on 82 | check execution (as supported by [`clj-http`](https://github.com/dakrone/clj-http)) 83 | or a function of context that will return such a map; defaults to 84 | `nil`. 85 | - `:opts`: a map of additional query options (as supported by 86 | [`clj-http`](https://github.com/dakrone/clj-http)) or a function of 87 | context that will return such a map; defaults to 88 | `{:throw-exceptions false}` since we want response success to be deduced 89 | by the `:response-result-fn` rather than treating unsuccessful statuses 90 | as exceptions; note that any timeouts passed in this query options map are 91 | ignored and should be set using `:connection-request-timeout`, 92 | `:connection-timeout` and `:socket-timeout`. 93 | - `:connection-request-timeout`: the [[salutem.core/duration]] to wait 94 | when obtaining a connection from the connection manager before 95 | considering the request failed; defaults to 5 seconds. 96 | - `:connection-timeout`: the [[salutem.core/duration]] to wait when 97 | establishing an HTTP connection before considering the request failed; 98 | defaults to 5 seconds. 99 | - `:socket-timeout`: the [[salutem.core/duration]] to wait while streaming 100 | response data since the last data was received before considering the 101 | request failed; defaults to 5 seconds. 102 | - `:successful-response-fn`: a function of context and the response from a 103 | request to the endpoint, returning true if the response was successful, 104 | false otherwise; by default uses [[successful?]]. 105 | - `:response-result-fn`: a function, of context and the response from a 106 | request to the endpoint, used to produce a result for the check; by 107 | default, a healthy result is returned if the response is successful 108 | according to `:successful-response-fn`, otherwise an unhealthy result is 109 | returned. 110 | - `:failure-reason-fn`: a function, of context and an exception, to 111 | determine the reason for a failure; by default uses [[failure-reason]]. 112 | - `:exception-result-fn`: a function, of context and an exception, used to 113 | produce a result for the check in the case that an exception is thrown; 114 | by default, an unhealthy result is returned including a `:salutem/reason` 115 | entry with the reason derived by `:failure-reason-fn` and a 116 | `:salutem/exception` entry containing the thrown exception. 117 | 118 | Additionally, if the URL parameter is instead a function, it will be called 119 | with the context map at check execution time in order to obtain the 120 | endpoint URL. 121 | 122 | If the returned check function is invoked with a context map including a 123 | `:logger` key with a 124 | [`cartus.core/Logger`](https://logicblocks.github.io/cartus/cartus.core.html#var-Logger) 125 | value, the check function will emit a number of log events whilst 126 | executing." 127 | ([url] (http-endpoint-check-fn url {})) 128 | ([url 129 | {:keys [method 130 | body 131 | headers 132 | query-params 133 | 134 | opts 135 | 136 | connection-request-timeout 137 | connection-timeout 138 | socket-timeout 139 | 140 | successful-response-fn 141 | response-result-fn 142 | 143 | failure-reason-fn 144 | exception-result-fn]}] 145 | ; Defaulting this way keeps argument documentation more readable while 146 | ; allowing defaults to be viewed more clearly using 'view source'. 147 | (let [method (or method :get) 148 | 149 | connection-request-timeout 150 | (or connection-request-timeout (time/new-duration 5 :seconds)) 151 | connection-timeout 152 | (or connection-timeout (time/new-duration 5 :seconds)) 153 | socket-timeout 154 | (or socket-timeout (time/new-duration 5 :seconds)) 155 | 156 | successful-response-fn 157 | (or successful-response-fn 158 | (fn [_ response] 159 | (successful? response))) 160 | response-result-fn 161 | (or response-result-fn 162 | (fn [context response] 163 | (if (successful-response-fn context response) 164 | (salutem/healthy) 165 | (salutem/unhealthy)))) 166 | 167 | failure-reason-fn 168 | (or failure-reason-fn 169 | (fn [_ exception] 170 | (failure-reason exception))) 171 | exception-result-fn 172 | (or exception-result-fn 173 | (fn [context exception] 174 | (salutem/unhealthy 175 | {:salutem/reason (failure-reason-fn context exception) 176 | :salutem/exception exception})))] 177 | (fn [context result-cb] 178 | (let [logger (get context :logger (cn/logger))] 179 | (try 180 | (let [endpoint-params 181 | {:url (resolve-if-fn url context) 182 | :method (resolve-if-fn method context) 183 | :body (resolve-if-fn body context) 184 | :headers (resolve-if-fn headers context) 185 | :query-params (resolve-if-fn query-params context)}] 186 | (log/info logger :salutem.check-fns.http-endpoint/check.starting 187 | endpoint-params) 188 | (http/request 189 | (merge 190 | endpoint-params 191 | {:throw-exceptions false} 192 | (resolve-if-fn opts context) 193 | {:async? true 194 | :connection-timeout (time/millis connection-timeout) 195 | :socket-timeout (time/millis socket-timeout) 196 | :connection-request-timeout 197 | (time/millis connection-request-timeout)}) 198 | (fn [response] 199 | (log/info logger 200 | :salutem.check-fns.http-endpoint/check.successful) 201 | (result-cb 202 | (response-result-fn 203 | context response))) 204 | (fn [exception] 205 | (log/warn logger :salutem.check-fns.http-endpoint/check.failed 206 | {:reason (failure-reason-fn context exception)} 207 | {:exception exception}) 208 | (result-cb 209 | (exception-result-fn 210 | context exception))))) 211 | (catch Exception exception 212 | (log/warn logger :salutem.check-fns.http-endpoint/check.failed 213 | {:reason (failure-reason-fn context exception)} 214 | {:exception exception}) 215 | (result-cb 216 | (exception-result-fn context exception))))))))) 217 | -------------------------------------------------------------------------------- /check-fns/http-endpoint/test/integration/salutem/check_fns/http_endpoint/async_test.clj: -------------------------------------------------------------------------------- 1 | (ns salutem.check-fns.http-endpoint.async-test 2 | (:require 3 | [clojure.test :refer :all] 4 | 5 | [clj-wiremock.core :as wiremock] 6 | 7 | [salutem.check-fns.http-endpoint.core :as scfhe] 8 | 9 | [salutem.test.support.ports :as ports])) 10 | 11 | (deftest http-endpoint-check-fn-executes-request-asynchronously 12 | (let [wiremock-port (ports/free-port) 13 | endpoint-url (str "http://localhost:" wiremock-port "/ping") 14 | 15 | check-fn (scfhe/http-endpoint-check-fn endpoint-url) 16 | 17 | context {} 18 | 19 | result-promise (promise) 20 | result-cb (partial deliver result-promise) 21 | 22 | order-atom (atom [])] 23 | (wiremock/with-wiremock [{:port wiremock-port}] 24 | (wiremock/with-stubs 25 | [{:port wiremock-port 26 | :req [:GET "/ping"] 27 | :res [200 {:body "pong" 28 | :fixedDelayMilliseconds 250}]}] 29 | (swap! order-atom conj :before) 30 | (check-fn context 31 | (fn [result] 32 | (swap! order-atom conj :finished) 33 | (result-cb result))) 34 | (swap! order-atom conj :after) 35 | 36 | (deref result-promise 500 nil))) 37 | 38 | (is (= [:before :after :finished] 39 | (deref order-atom))))) 40 | -------------------------------------------------------------------------------- /check-fns/http-endpoint/test/integration/salutem/check_fns/http_endpoint/timeout_test.clj: -------------------------------------------------------------------------------- 1 | (ns salutem.check-fns.http-endpoint.timeout-test 2 | (:require 3 | [clojure.test :refer :all] 4 | 5 | [clj-http.conn-mgr :as conn-mgr] 6 | [clj-wiremock.core :as wiremock] 7 | 8 | [tick.core :as time] 9 | 10 | [salutem.core :as salutem] 11 | [salutem.check-fns.http-endpoint.core :as scfhe] 12 | 13 | [salutem.test.support.ports :as ports])) 14 | 15 | (deftest http-endpoint-check-fn-executes-request-asynchronously 16 | (let [wiremock-port (ports/free-port) 17 | endpoint-url (str "http://localhost:" wiremock-port "/ping") 18 | 19 | check-fn (scfhe/http-endpoint-check-fn endpoint-url) 20 | 21 | context {} 22 | 23 | result-promise (promise) 24 | result-cb (partial deliver result-promise) 25 | 26 | order-atom (atom [])] 27 | (wiremock/with-wiremock [{:port wiremock-port}] 28 | (wiremock/with-stubs 29 | [{:port wiremock-port 30 | :req [:GET "/ping"] 31 | :res [200 {:body "pong" 32 | :fixedDelayMilliseconds 250}]}] 33 | (swap! order-atom conj :before) 34 | (check-fn context 35 | (fn [result] 36 | (swap! order-atom conj :finished) 37 | (result-cb result))) 38 | (swap! order-atom conj :after) 39 | 40 | (deref result-promise 500 nil))) 41 | 42 | (is (= [:before :after :finished] 43 | (deref order-atom))))) 44 | 45 | (deftest http-endpoint-check-fn-times-out-after-supplied-socket-timeout 46 | (let [endpoint-port (ports/free-port) 47 | endpoint-url (str "http://localhost:" endpoint-port "/ping") 48 | 49 | check-fn (scfhe/http-endpoint-check-fn endpoint-url 50 | {:socket-timeout (time/new-duration 500 :millis)}) 51 | 52 | context {} 53 | 54 | result-promise (promise) 55 | result-cb (partial deliver result-promise)] 56 | (wiremock/with-wiremock [{:port endpoint-port}] 57 | (wiremock/with-stubs 58 | [{:port endpoint-port 59 | :req [:GET "/ping"] 60 | :res [200 {:body "pong" 61 | :fixedDelayMilliseconds 1000}]}] 62 | 63 | (check-fn context result-cb) 64 | 65 | (let [result (deref result-promise 1500 nil)] 66 | (is (salutem/unhealthy? result)) 67 | (is (= :timed-out (:salutem/reason result)))))))) 68 | 69 | (deftest http-endpoint-check-fn-times-out-after-supplied-connection-timeout 70 | (let [unroutable-address "10.0.0.0" 71 | endpoint-url (str "http://" unroutable-address "/ping") 72 | 73 | connection-manager (conn-mgr/make-reusable-async-conn-manager {}) 74 | 75 | check-fn (scfhe/http-endpoint-check-fn endpoint-url 76 | {:connection-timeout 77 | (time/new-duration 500 :millis) 78 | :opts 79 | ; Need to manage connection manager directly to control when 80 | ; shutdown wait time is incurred. 81 | {:connection-manager connection-manager}}) 82 | 83 | context {} 84 | 85 | result-promise (promise) 86 | result-cb (partial deliver result-promise)] 87 | (check-fn context result-cb) 88 | 89 | (let [result (deref result-promise 1500 nil)] 90 | (is (salutem/unhealthy? result)) 91 | (is (= :timed-out (:salutem/reason result)))) 92 | 93 | (conn-mgr/shutdown-manager connection-manager))) 94 | -------------------------------------------------------------------------------- /check-fns/http-endpoint/test/shared/salutem/test/support/ports.clj: -------------------------------------------------------------------------------- 1 | (ns salutem.test.support.ports 2 | (:import [java.net ServerSocket])) 3 | 4 | (defn free-port [] 5 | (with-open [socket (ServerSocket. 0)] 6 | (.getLocalPort socket))) 7 | -------------------------------------------------------------------------------- /check-fns/project.clj: -------------------------------------------------------------------------------- 1 | (defproject io.logicblocks/salutem.check-fns "0.1.9-RC4" 2 | :description "A set of standard check functions for salutem." 3 | 4 | :parent-project {:path "../parent/project.clj" 5 | :inherit [:scm 6 | :url 7 | :license 8 | :plugins 9 | :deploy-repositories 10 | :managed-dependencies]} 11 | 12 | :plugins [[lein-parent "0.3.9"]] 13 | 14 | :dependencies [[io.logicblocks/salutem.check-fns.data-source] 15 | [io.logicblocks/salutem.check-fns.http-endpoint]] 16 | 17 | :source-paths [] 18 | :test-paths [] 19 | :resource-paths []) 20 | -------------------------------------------------------------------------------- /config/gpg/jonas.gpg.public: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | 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 59 | =e5EY 60 | -----END PGP PUBLIC KEY BLOCK----- -------------------------------------------------------------------------------- /config/gpg/liam.gpg.public: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | 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 40 | =Z4+L 41 | -----END PGP PUBLIC KEY BLOCK----- 42 | -------------------------------------------------------------------------------- /config/gpg/toby.gpg.public: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | 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 105 | VryshT5m6VKxVqJv 106 | =vHmq 107 | -----END PGP PUBLIC KEY BLOCK----- 108 | -------------------------------------------------------------------------------- /config/linter.clj: -------------------------------------------------------------------------------- 1 | (disable-warning 2 | {:linter :suspicious-expression 3 | :for-macro 'clojure.core/or 4 | :if-inside-macroexpansion-of #{'clojure.core.async/go 5 | 'clojure.core.async/alt! 6 | 'clojure.core.async/alt!!} 7 | :within-depth 18}) 8 | -------------------------------------------------------------------------------- /config/secrets/ci/encryption.passphrase: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/config/secrets/ci/encryption.passphrase -------------------------------------------------------------------------------- /config/secrets/ci/gpg.private: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/config/secrets/ci/gpg.private -------------------------------------------------------------------------------- /config/secrets/ci/gpg.public: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/config/secrets/ci/gpg.public -------------------------------------------------------------------------------- /config/secrets/ci/gpg.uid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/config/secrets/ci/gpg.uid -------------------------------------------------------------------------------- /config/secrets/ci/ssh.private: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/config/secrets/ci/ssh.private -------------------------------------------------------------------------------- /config/secrets/ci/ssh.public: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/config/secrets/ci/ssh.public -------------------------------------------------------------------------------- /config/secrets/circle_ci/config.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/config/secrets/circle_ci/config.yaml -------------------------------------------------------------------------------- /config/secrets/clojars/credentials.clj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/config/secrets/clojars/credentials.clj -------------------------------------------------------------------------------- /config/secrets/clojars/credentials.clj.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/config/secrets/clojars/credentials.clj.gpg -------------------------------------------------------------------------------- /config/secrets/github/config.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/config/secrets/github/config.yaml -------------------------------------------------------------------------------- /core/dev/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.java.javadoc :refer [javadoc]] 5 | [clojure.pprint :refer [pprint]] 6 | [clojure.reflect :refer [reflect]] 7 | [clojure.repl :refer [apropos dir doc find-doc pst source]] 8 | [clojure.set :as set] 9 | [clojure.string :as string] 10 | [clojure.tools.namespace.repl :refer [refresh refresh-all clear]] 11 | 12 | [eftest.runner :refer [find-tests run-tests]])) 13 | 14 | (defn run-tests-in [& dirs] 15 | (run-tests 16 | (find-tests dirs) 17 | {:multithread? false})) 18 | -------------------------------------------------------------------------------- /core/project.clj: -------------------------------------------------------------------------------- 1 | (defproject io.logicblocks/salutem.core "0.1.9-RC4" 2 | :description "A health check library for sync / async health checks." 3 | 4 | :parent-project {:path "../parent/project.clj" 5 | :inherit [:scm 6 | :url 7 | :license 8 | :plugins 9 | [:profiles :parent-shared] 10 | [:profiles :parent-reveal] 11 | [:profiles :parent-dev] 12 | [:profiles :parent-unit] 13 | [:profiles :parent-performance] 14 | :deploy-repositories 15 | :managed-dependencies 16 | :cloverage 17 | :bikeshed 18 | :cljfmt 19 | :eastwood]} 20 | 21 | :plugins [[lein-parent "0.3.8"]] 22 | 23 | :dependencies [[org.clojure/core.async] 24 | 25 | [io.logicblocks/cartus.core] 26 | [io.logicblocks/cartus.null] 27 | 28 | [tick]] 29 | 30 | :profiles {:shared {:dependencies [[tortue/spy]]} 31 | :reveal [:parent-reveal] 32 | :dev [:parent-dev :shared] 33 | :unit [:parent-unit :shared] 34 | :performance [:parent-performance :shared]} 35 | 36 | :test-paths ["test/shared" 37 | "test/unit" 38 | "test/performance"] 39 | :resource-paths []) 40 | -------------------------------------------------------------------------------- /core/src/salutem/core/checks.clj: -------------------------------------------------------------------------------- 1 | (ns salutem.core.checks 2 | "Provides constructors, predicates and evaluation functions for checks." 3 | (:require 4 | [clojure.core.async :as async] 5 | 6 | [tick.core :as t] 7 | 8 | [cartus.core :as log] 9 | [cartus.null :as cartus-null] 10 | 11 | [salutem.core.results :as results])) 12 | 13 | (defn- check 14 | ([check-name check-fn] 15 | (check check-name check-fn {})) 16 | ([check-name check-fn 17 | {:keys [salutem/timeout] 18 | :or {timeout (t/new-duration 10 :seconds)} 19 | :as opts}] 20 | (merge 21 | opts 22 | {:salutem/name check-name 23 | :salutem/check-fn check-fn 24 | :salutem/timeout timeout}))) 25 | 26 | (defn background-check 27 | "Constructs a background check with the provided name and check function. 28 | 29 | A background check is one that is evaluated periodically with the result 30 | cached in a registry until the next evaluation, conducted by a maintenance 31 | pipeline, which will occur once the time to re-evaluation of the check has 32 | passed. 33 | 34 | Background checks are useful for external dependencies where it is 35 | important not to perform the check too frequently and where the health 36 | status only needs to be accurate on the order of the time to re-evaluation. 37 | 38 | Takes the following parameters: 39 | 40 | - `check-name`: a keyword representing the name of the check 41 | - `check-fn`: an arity-2 function, with the first argument being a context 42 | map as provided during evaluation or at maintenance pipeline construction 43 | and the second argument being a callback function which should be called 44 | with the result of the check to signal the check is complete; note, check 45 | functions _must_ be non-blocking. 46 | - `opts`: an optional map of additional options for the check, containing: 47 | - `:salutem/timeout`: a [[salutem.time/duration]] representing the amount 48 | of time to wait for the check to complete before considering it failed, 49 | defaulting to 10 seconds. 50 | - `:salutem/time-to-re-evaluation`: a [[salutem.time/duration]] 51 | representing the time to wait after a check is evaluated before 52 | attempting to re-evaluate it, defaulting to 10 seconds. 53 | 54 | Any extra entries provided in the `opts` map are retained on the check for 55 | later use. 56 | 57 | Note that a result for a background check may live for longer than the 58 | time to re-evaluation since evaluation takes time and the result will 59 | continue to be returned from the registry whenever the check is resolved 60 | until the evaluation has completed and the new result has been added to the 61 | registry." 62 | ([check-name check-fn] 63 | (background-check check-name check-fn {})) 64 | ([check-name check-fn opts] 65 | (let [time-to-re-evaluation 66 | (or 67 | (:salutem/time-to-re-evaluation opts) 68 | (:salutem/ttl opts) 69 | (t/new-duration 10 :seconds))] 70 | (check check-name check-fn 71 | (merge 72 | {:salutem/time-to-re-evaluation time-to-re-evaluation} 73 | (dissoc opts :salutem/time-to-re-evaluation :salutem/ttl) 74 | {:salutem/type :background}))))) 75 | 76 | (defn realtime-check 77 | "Constructs a realtime check with the provided name and check function. 78 | 79 | A realtime check is one that is re-evaluated whenever the check is resolved, 80 | with no caching of results taking place. 81 | 82 | Realtime checks are useful when the accuracy of the check needs to be very 83 | high or where the check itself is inexpensive. 84 | 85 | Takes the following parameters: 86 | 87 | - `check-name`: a keyword representing the name of the check 88 | - `check-fn`: an arity-2 function, with the first argument being a context 89 | map as provided during evaluation or at maintenance pipeline construction 90 | and the second argument being a callback function which should be called 91 | with the result fo the check to signal the check is complete; note, check 92 | functions _must_ be non-blocking. 93 | - `opts`: an optional map of additional options for the check, containing: 94 | - `:salutem/timeout`: a [[salutem.time/duration]] representing the amount 95 | of time to wait for the check to complete before considering it failed, 96 | defaulting to 10 seconds. 97 | 98 | Any extra entries provided in the `opts` map are retained on the check for 99 | later use." 100 | ([check-name check-fn] 101 | (realtime-check check-name check-fn {})) 102 | ([check-name check-fn opts] 103 | (check check-name check-fn 104 | (merge 105 | opts 106 | {:salutem/type :realtime})))) 107 | 108 | (defn background? 109 | "Returns `true` if the provided check is a background check, `false` 110 | otherwise." 111 | [check] 112 | (= (:salutem/type check) :background)) 113 | 114 | (defn realtime? 115 | "Returns `true` if the provided check is a realtime check, `false` 116 | otherwise." 117 | [check] 118 | (= (:salutem/type check) :realtime)) 119 | 120 | (defn check-name 121 | "Returns the name of the provided check." 122 | [check] 123 | (:salutem/name check)) 124 | 125 | (defn attempt 126 | "Attempts to obtain a result for a check, handling timeouts and exceptions. 127 | 128 | Takes the following parameters: 129 | 130 | - `dependencies`: A map of dependencies used by `attempt` in obtaining the 131 | result, currently supporting only a `:logger` entry with a 132 | [`cartus.core/Logger`](https://logicblocks.github.io/cartus/cartus.core.html#var-Logger) 133 | value. 134 | - `trigger-id`: An ID identifying the attempt in any subsequently produced 135 | messages and used in logging. 136 | - `check`: the check to be attempted. 137 | - `context`: an optional map containing arbitrary context required by the 138 | check in order to run and passed to the check functions as the first 139 | argument; defaults to an empty map. 140 | - `result-channel`: an optional channel on which to send the result message; 141 | defaults to a channel with a buffer length of 1. 142 | 143 | The attempt is performed asynchronously and the result channel is returned 144 | immediately. 145 | 146 | In the case that the attempt takes longer than the check's timeout, an 147 | unhealthy result is produced, including `:salutem/reason` as `:timed-out`. 148 | 149 | In the case that the attempt throws an exception, an unhealthy result is 150 | produced, including `:salutem/reason` as `:exception-thrown` and including 151 | the exception at `:salutem/exception`. 152 | 153 | In all other cases, the result produced by the check is passed on to the 154 | result channel. 155 | 156 | All produced results include a `:salutem/evaluation-duration` entry with the 157 | time taken to obtain the result, which can be overridden within check 158 | functions if required." 159 | ([dependencies trigger-id check] 160 | (attempt dependencies trigger-id check {})) 161 | ([dependencies trigger-id check context] 162 | (attempt dependencies trigger-id check context (async/chan 1))) 163 | ([dependencies trigger-id check context result-channel] 164 | (let [logger (or (:logger dependencies) (cartus-null/logger)) 165 | check-name (:salutem/name check)] 166 | (async/go 167 | (let [{:keys [salutem/check-fn salutem/timeout]} check 168 | callback-channel (async/chan) 169 | exception-channel (async/chan 1) 170 | before (t/now)] 171 | (log/info logger ::attempt.starting 172 | {:trigger-id trigger-id 173 | :check-name check-name}) 174 | (try 175 | (check-fn context 176 | (fn [result] 177 | (async/put! callback-channel result))) 178 | (catch Exception exception 179 | (async/>! exception-channel exception))) 180 | (async/alt! 181 | exception-channel 182 | ([exception] 183 | (let [after (t/now) 184 | duration (t/between before after)] 185 | (log/info logger ::attempt.threw-exception 186 | {:trigger-id trigger-id 187 | :check-name check-name 188 | :exception exception}) 189 | (async/>! result-channel 190 | {:trigger-id trigger-id 191 | :check check 192 | :result (results/unhealthy 193 | {:salutem/reason :threw-exception 194 | :salutem/exception exception 195 | :salutem/evaluation-duration duration})}))) 196 | 197 | (async/timeout (t/millis timeout)) 198 | (let [after (t/now) 199 | duration (t/between before after)] 200 | (log/info logger ::attempt.timed-out 201 | {:trigger-id trigger-id 202 | :check-name check-name}) 203 | (async/>! result-channel 204 | {:trigger-id trigger-id 205 | :check check 206 | :result (results/unhealthy 207 | {:salutem/reason :timed-out 208 | :salutem/evaluation-duration duration})})) 209 | 210 | callback-channel 211 | ([result] 212 | (let [after (t/now) 213 | duration (t/between before after) 214 | result (results/prepend result 215 | {:salutem/evaluation-duration duration})] 216 | (log/info logger ::attempt.completed 217 | {:trigger-id trigger-id 218 | :check-name check-name 219 | :result result}) 220 | (async/>! result-channel 221 | {:trigger-id trigger-id 222 | :check check 223 | :result result}))) 224 | 225 | :priority true) 226 | (async/close! exception-channel) 227 | (async/close! callback-channel)))) 228 | result-channel)) 229 | 230 | (defn- evaluation-attempt [check context] 231 | (let [logger (or (:logger context) (cartus-null/logger)) 232 | trigger-id (or (:trigger-id context) :ad-hoc)] 233 | (attempt {:logger logger} trigger-id check context 234 | (async/chan 1 (map :result))))) 235 | 236 | (defn evaluate 237 | "Evaluates the provided check, returning the result of the evaluation. 238 | 239 | Optionally takes a context map containing arbitrary context required by the 240 | check in order to run and passed to the check function as the first argument. 241 | 242 | By default, the check is evaluated synchronously. If a callback function is 243 | provided, the function starts evaluation asynchronously, returns immediately 244 | and invokes the callback function with the result once available." 245 | ([check] (evaluate check {})) 246 | ([check context] 247 | (async/> (t/now) (t/new-duration 1 :seconds))] 37 | (is (t/> after (:salutem/evaluated-at result) before)))) 38 | 39 | (deftest creates-result-with-specified-evaluated-at-datetime-when-provided 40 | (let [evaluated-at (t/<< (t/now) (t/new-period 1 :weeks)) 41 | result (results/healthy {:salutem/evaluated-at evaluated-at})] 42 | (is (= (:salutem/evaluated-at result) evaluated-at)))) 43 | 44 | (deftest creates-result-with-retained-extra-data 45 | (let [result (results/healthy {:thing-1 "one" :thing-2 "two"})] 46 | (is (= (:thing-1 result) "one")) 47 | (is (= (:thing-2 result) "two")))) 48 | 49 | (deftest creates-result-including-evaluated-at-when-extra-data-supplied 50 | (let [before (t/<< (t/now) (t/new-duration 1 :seconds)) 51 | result (results/healthy {:thing-1 "one" :thing-2 "two"}) 52 | after (t/>> (t/now) (t/new-duration 1 :seconds))] 53 | (is (t/> after (:salutem/evaluated-at result) before)))) 54 | 55 | (deftest status-returns-result-status 56 | (let [healthy-result (results/healthy) 57 | unhealthy-result (results/unhealthy) 58 | pending-result (results/result :pending)] 59 | (is (= (results/status healthy-result) :healthy)) 60 | (is (= (results/status unhealthy-result) :unhealthy)) 61 | (is (= (results/status pending-result) :pending)))) 62 | 63 | (deftest is-always-outdated-if-check-is-realtime 64 | (let [check (checks/realtime-check :thing 65 | (fn [_ result-cb] 66 | (result-cb (results/healthy)))) 67 | result (results/healthy 68 | {:salutem/evaluated-at 69 | (t/<< (t/now) (t/new-duration 60 :seconds))})] 70 | (is (true? (results/outdated? result check))))) 71 | 72 | (deftest is-outdated-if-evaluated-at-older-than-now-minus-time-to-re-evaluation 73 | (let [check (checks/background-check :thing 74 | (fn [_ result-cb] 75 | (result-cb (results/healthy))) 76 | {:salutem/time-to-re-evaluation (time/duration 30 :seconds)}) 77 | result (results/healthy 78 | {:salutem/evaluated-at 79 | (t/<< (t/now) (t/new-duration 60 :seconds))})] 80 | (is (true? (results/outdated? result check))))) 81 | 82 | (deftest 83 | is-not-outdated-if-evaluated-at-newer-than-now-minus-time-to-re-evaluation 84 | (let [check (checks/background-check :thing 85 | (fn [_ result-cb] 86 | (result-cb (results/healthy))) 87 | {:salutem/time-to-re-evaluation (time/duration 60 :seconds)}) 88 | result (results/healthy 89 | {:salutem/evaluated-at 90 | (t/<< (t/now) (t/new-duration 30 :seconds))})] 91 | (is (false? (results/outdated? result check))))) 92 | 93 | (deftest 94 | is-outdated-relative-to-provided-instant-when-older-than-time-to-re-evaluation 95 | (let [relative-to-instant (t/<< (t/now) (t/new-duration 2 :minutes)) 96 | check (checks/background-check :thing 97 | (fn [_ result-cb] 98 | (result-cb (results/healthy))) 99 | {:salutem/time-to-re-evaluation (time/duration 30 :seconds)}) 100 | result (results/healthy 101 | {:salutem/evaluated-at 102 | (t/<< (t/now) (t/new-duration 151 :seconds))})] 103 | (is (true? (results/outdated? result check relative-to-instant))))) 104 | 105 | (deftest 106 | is-not-outdated-relative-to-instant-when-newer-than-time-to-re-evaluation 107 | (let [relative-to-instant (t/<< (t/now) (t/new-duration 2 :minutes)) 108 | check (checks/background-check :thing 109 | (fn [_ result-cb] 110 | (result-cb (results/healthy))) 111 | {:salutem/time-to-re-evaluation (time/duration 30 :seconds)}) 112 | result (results/healthy 113 | {:salutem/evaluated-at 114 | (t/<< (t/now) (t/new-duration 149 :seconds))})] 115 | (is (false? (results/outdated? result check relative-to-instant))))) 116 | 117 | (deftest treats-a-nil-result-as-outdated 118 | (let [check (checks/background-check :thing 119 | (fn [_ result-cb] 120 | (result-cb (results/healthy))) 121 | {:salutem/time-to-re-evaluation (time/duration 30 :seconds)}) 122 | result nil] 123 | (is (true? (results/outdated? result check))))) 124 | -------------------------------------------------------------------------------- /core/test/unit/salutem/core/time_test.clj: -------------------------------------------------------------------------------- 1 | (ns salutem.core.time-test 2 | (:require 3 | [clojure.test :refer :all] 4 | 5 | [tick.core :as t] 6 | 7 | [salutem.core.time :as st])) 8 | 9 | (deftest constructs-duration-will-specified-values 10 | (let [cases [[100 :millis] 11 | [5 :seconds] 12 | [10 :minutes]]] 13 | (doseq [[numeral unit] cases] 14 | (is (= (st/duration numeral unit) 15 | (t/new-duration numeral unit)))))) 16 | -------------------------------------------------------------------------------- /docs/css/default.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Helvetica, Arial, sans-serif; 3 | font-size: 15px; 4 | } 5 | 6 | pre, code { 7 | font-family: Monaco, DejaVu Sans Mono, Consolas, monospace; 8 | font-size: 9pt; 9 | margin: 15px 0; 10 | } 11 | 12 | h1 { 13 | font-weight: normal; 14 | font-size: 29px; 15 | margin: 10px 0 2px 0; 16 | padding: 0; 17 | } 18 | 19 | h2 { 20 | font-weight: normal; 21 | font-size: 25px; 22 | } 23 | 24 | h5.license { 25 | margin: 9px 0 22px 0; 26 | color: #555; 27 | font-weight: normal; 28 | font-size: 12px; 29 | font-style: italic; 30 | } 31 | 32 | .document h1, .namespace-index h1 { 33 | font-size: 32px; 34 | margin-top: 12px; 35 | } 36 | 37 | #header, #content, .sidebar { 38 | position: fixed; 39 | } 40 | 41 | #header { 42 | top: 0; 43 | left: 0; 44 | right: 0; 45 | height: 22px; 46 | color: #f5f5f5; 47 | padding: 5px 7px; 48 | } 49 | 50 | #content { 51 | top: 32px; 52 | right: 0; 53 | bottom: 0; 54 | overflow: auto; 55 | background: #fff; 56 | color: #333; 57 | padding: 0 18px; 58 | } 59 | 60 | .sidebar { 61 | position: fixed; 62 | top: 32px; 63 | bottom: 0; 64 | overflow: auto; 65 | } 66 | 67 | .sidebar.primary { 68 | background: #e2e2e2; 69 | border-right: solid 1px #cccccc; 70 | left: 0; 71 | width: 250px; 72 | } 73 | 74 | .sidebar.secondary { 75 | background: #f2f2f2; 76 | border-right: solid 1px #d7d7d7; 77 | left: 251px; 78 | width: 200px; 79 | } 80 | 81 | #content.namespace-index, #content.document { 82 | left: 251px; 83 | } 84 | 85 | #content.namespace-docs { 86 | left: 452px; 87 | } 88 | 89 | #content.document { 90 | padding-bottom: 10%; 91 | } 92 | 93 | #header { 94 | background: #3f3f3f; 95 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.4); 96 | z-index: 100; 97 | } 98 | 99 | #header h1 { 100 | margin: 0; 101 | padding: 0; 102 | font-size: 18px; 103 | font-weight: lighter; 104 | text-shadow: -1px -1px 0px #333; 105 | } 106 | 107 | #header h1 .project-version { 108 | font-weight: normal; 109 | } 110 | 111 | .project-version { 112 | padding-left: 0.15em; 113 | } 114 | 115 | #header a, .sidebar a { 116 | display: block; 117 | text-decoration: none; 118 | } 119 | 120 | #header a { 121 | color: #f5f5f5; 122 | } 123 | 124 | .sidebar a { 125 | color: #333; 126 | } 127 | 128 | #header h2 { 129 | float: right; 130 | font-size: 9pt; 131 | font-weight: normal; 132 | margin: 4px 3px; 133 | padding: 0; 134 | color: #bbb; 135 | } 136 | 137 | #header h2 a { 138 | display: inline; 139 | } 140 | 141 | .sidebar h3 { 142 | margin: 0; 143 | padding: 10px 13px 0 13px; 144 | font-size: 19px; 145 | font-weight: lighter; 146 | } 147 | 148 | .sidebar h3 a { 149 | color: #444; 150 | } 151 | 152 | .sidebar h3.no-link { 153 | color: #636363; 154 | } 155 | 156 | .sidebar ul { 157 | padding: 7px 0 6px 0; 158 | margin: 0; 159 | } 160 | 161 | .sidebar ul.index-link { 162 | padding-bottom: 4px; 163 | } 164 | 165 | .sidebar li { 166 | display: block; 167 | vertical-align: middle; 168 | } 169 | 170 | .sidebar li a, .sidebar li .no-link { 171 | border-left: 3px solid transparent; 172 | padding: 0 10px; 173 | white-space: nowrap; 174 | } 175 | 176 | .sidebar li .no-link { 177 | display: block; 178 | color: #777; 179 | font-style: italic; 180 | } 181 | 182 | .sidebar li .inner { 183 | display: inline-block; 184 | padding-top: 7px; 185 | height: 24px; 186 | } 187 | 188 | .sidebar li a, .sidebar li .tree { 189 | height: 31px; 190 | } 191 | 192 | .depth-1 .inner { padding-left: 2px; } 193 | .depth-2 .inner { padding-left: 6px; } 194 | .depth-3 .inner { padding-left: 20px; } 195 | .depth-4 .inner { padding-left: 34px; } 196 | .depth-5 .inner { padding-left: 48px; } 197 | .depth-6 .inner { padding-left: 62px; } 198 | 199 | .sidebar li .tree { 200 | display: block; 201 | float: left; 202 | position: relative; 203 | top: -10px; 204 | margin: 0 4px 0 0; 205 | padding: 0; 206 | } 207 | 208 | .sidebar li.depth-1 .tree { 209 | display: none; 210 | } 211 | 212 | .sidebar li .tree .top, .sidebar li .tree .bottom { 213 | display: block; 214 | margin: 0; 215 | padding: 0; 216 | width: 7px; 217 | } 218 | 219 | .sidebar li .tree .top { 220 | border-left: 1px solid #aaa; 221 | border-bottom: 1px solid #aaa; 222 | height: 19px; 223 | } 224 | 225 | .sidebar li .tree .bottom { 226 | height: 22px; 227 | } 228 | 229 | .sidebar li.branch .tree .bottom { 230 | border-left: 1px solid #aaa; 231 | } 232 | 233 | .sidebar.primary li.current a { 234 | border-left: 3px solid #a33; 235 | color: #a33; 236 | } 237 | 238 | .sidebar.secondary li.current a { 239 | border-left: 3px solid #33a; 240 | color: #33a; 241 | } 242 | 243 | .namespace-index h2 { 244 | margin: 30px 0 0 0; 245 | } 246 | 247 | .namespace-index h3 { 248 | font-size: 16px; 249 | font-weight: bold; 250 | margin-bottom: 0; 251 | } 252 | 253 | .namespace-index .topics { 254 | padding-left: 30px; 255 | margin: 11px 0 0 0; 256 | } 257 | 258 | .namespace-index .topics li { 259 | padding: 5px 0; 260 | } 261 | 262 | .namespace-docs h3 { 263 | font-size: 18px; 264 | font-weight: bold; 265 | } 266 | 267 | .public h3 { 268 | margin: 0; 269 | float: left; 270 | } 271 | 272 | .usage { 273 | clear: both; 274 | } 275 | 276 | .public { 277 | margin: 0; 278 | border-top: 1px solid #e0e0e0; 279 | padding-top: 14px; 280 | padding-bottom: 6px; 281 | } 282 | 283 | .public:last-child { 284 | margin-bottom: 20%; 285 | } 286 | 287 | .members .public:last-child { 288 | margin-bottom: 0; 289 | } 290 | 291 | .members { 292 | margin: 15px 0; 293 | } 294 | 295 | .members h4 { 296 | color: #555; 297 | font-weight: normal; 298 | font-variant: small-caps; 299 | margin: 0 0 5px 0; 300 | } 301 | 302 | .members .inner { 303 | padding-top: 5px; 304 | padding-left: 12px; 305 | margin-top: 2px; 306 | margin-left: 7px; 307 | border-left: 1px solid #bbb; 308 | } 309 | 310 | #content .members .inner h3 { 311 | font-size: 12pt; 312 | } 313 | 314 | .members .public { 315 | border-top: none; 316 | margin-top: 0; 317 | padding-top: 6px; 318 | padding-bottom: 0; 319 | } 320 | 321 | .members .public:first-child { 322 | padding-top: 0; 323 | } 324 | 325 | h4.type, 326 | h4.dynamic, 327 | h4.added, 328 | h4.deprecated { 329 | float: left; 330 | margin: 3px 10px 15px 0; 331 | font-size: 15px; 332 | font-weight: bold; 333 | font-variant: small-caps; 334 | } 335 | 336 | .public h4.type, 337 | .public h4.dynamic, 338 | .public h4.added, 339 | .public h4.deprecated { 340 | font-size: 13px; 341 | font-weight: bold; 342 | margin: 3px 0 0 10px; 343 | } 344 | 345 | .members h4.type, 346 | .members h4.added, 347 | .members h4.deprecated { 348 | margin-top: 1px; 349 | } 350 | 351 | h4.type { 352 | color: #717171; 353 | } 354 | 355 | h4.dynamic { 356 | color: #9933aa; 357 | } 358 | 359 | h4.added { 360 | color: #508820; 361 | } 362 | 363 | h4.deprecated { 364 | color: #880000; 365 | } 366 | 367 | .namespace { 368 | margin-bottom: 30px; 369 | } 370 | 371 | .namespace:last-child { 372 | margin-bottom: 10%; 373 | } 374 | 375 | .index { 376 | padding: 0; 377 | font-size: 80%; 378 | margin: 15px 0; 379 | line-height: 16px; 380 | } 381 | 382 | .index * { 383 | display: inline; 384 | } 385 | 386 | .index p { 387 | padding-right: 3px; 388 | } 389 | 390 | .index li { 391 | padding-right: 5px; 392 | } 393 | 394 | .index ul { 395 | padding-left: 0; 396 | } 397 | 398 | .type-sig { 399 | clear: both; 400 | color: #088; 401 | } 402 | 403 | .type-sig pre { 404 | padding-top: 10px; 405 | margin: 0; 406 | } 407 | 408 | .usage code { 409 | display: block; 410 | color: #008; 411 | margin: 2px 0; 412 | } 413 | 414 | .usage code:first-child { 415 | padding-top: 10px; 416 | } 417 | 418 | p { 419 | margin: 15px 0; 420 | } 421 | 422 | .public p:first-child, .public pre.plaintext { 423 | margin-top: 12px; 424 | } 425 | 426 | .doc { 427 | margin: 0 0 26px 0; 428 | clear: both; 429 | } 430 | 431 | .public .doc { 432 | margin: 0; 433 | } 434 | 435 | .namespace-index .doc { 436 | margin-bottom: 20px; 437 | } 438 | 439 | .namespace-index .namespace .doc { 440 | margin-bottom: 10px; 441 | } 442 | 443 | .markdown p, .markdown li, .markdown dt, .markdown dd, .markdown td { 444 | line-height: 22px; 445 | } 446 | 447 | .markdown li { 448 | padding: 2px 0; 449 | } 450 | 451 | .markdown h2 { 452 | font-weight: normal; 453 | font-size: 25px; 454 | margin: 30px 0 10px 0; 455 | } 456 | 457 | .markdown h3 { 458 | font-weight: normal; 459 | font-size: 20px; 460 | margin: 30px 0 0 0; 461 | } 462 | 463 | .markdown h4 { 464 | font-size: 15px; 465 | margin: 22px 0 -4px 0; 466 | } 467 | 468 | .doc, .public, .namespace .index { 469 | max-width: 680px; 470 | overflow-x: visible; 471 | } 472 | 473 | .markdown pre > code { 474 | display: block; 475 | padding: 10px; 476 | } 477 | 478 | .markdown pre > code, .src-link a { 479 | border: 1px solid #e4e4e4; 480 | border-radius: 2px; 481 | } 482 | 483 | .markdown code:not(.hljs), .src-link a { 484 | background: #f6f6f6; 485 | } 486 | 487 | pre.deps { 488 | display: inline-block; 489 | margin: 0 10px; 490 | border: 1px solid #e4e4e4; 491 | border-radius: 2px; 492 | padding: 10px; 493 | background-color: #f6f6f6; 494 | } 495 | 496 | .markdown hr { 497 | border-style: solid; 498 | border-top: none; 499 | color: #ccc; 500 | } 501 | 502 | .doc ul, .doc ol { 503 | padding-left: 30px; 504 | } 505 | 506 | .doc table { 507 | border-collapse: collapse; 508 | margin: 0 10px; 509 | } 510 | 511 | .doc table td, .doc table th { 512 | border: 1px solid #dddddd; 513 | padding: 4px 6px; 514 | } 515 | 516 | .doc table th { 517 | background: #f2f2f2; 518 | } 519 | 520 | .doc dl { 521 | margin: 0 10px 20px 10px; 522 | } 523 | 524 | .doc dl dt { 525 | font-weight: bold; 526 | margin: 0; 527 | padding: 3px 0; 528 | border-bottom: 1px solid #ddd; 529 | } 530 | 531 | .doc dl dd { 532 | padding: 5px 0; 533 | margin: 0 0 5px 10px; 534 | } 535 | 536 | .doc abbr { 537 | border-bottom: 1px dotted #333; 538 | font-variant: none; 539 | cursor: help; 540 | } 541 | 542 | .src-link { 543 | margin-bottom: 15px; 544 | } 545 | 546 | .src-link a { 547 | font-size: 70%; 548 | padding: 1px 4px; 549 | text-decoration: none; 550 | color: #5555bb; 551 | } 552 | -------------------------------------------------------------------------------- /docs/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | github.com style (c) Vasily Polovnyov 3 | */ 4 | 5 | .hljs { 6 | display: block; 7 | overflow-x: auto; 8 | padding: 0.5em; 9 | color: #333; 10 | background: #f8f8f8; 11 | } 12 | 13 | .hljs-comment, 14 | .hljs-quote { 15 | color: #998; 16 | font-style: italic; 17 | } 18 | 19 | .hljs-keyword, 20 | .hljs-selector-tag, 21 | .hljs-subst { 22 | color: #333; 23 | font-weight: bold; 24 | } 25 | 26 | .hljs-number, 27 | .hljs-literal, 28 | .hljs-variable, 29 | .hljs-template-variable, 30 | .hljs-tag .hljs-attr { 31 | color: #008080; 32 | } 33 | 34 | .hljs-string, 35 | .hljs-doctag { 36 | color: #d14; 37 | } 38 | 39 | .hljs-title, 40 | .hljs-section, 41 | .hljs-selector-id { 42 | color: #900; 43 | font-weight: bold; 44 | } 45 | 46 | .hljs-subst { 47 | font-weight: normal; 48 | } 49 | 50 | .hljs-type, 51 | .hljs-class .hljs-title { 52 | color: #458; 53 | font-weight: bold; 54 | } 55 | 56 | .hljs-tag, 57 | .hljs-name, 58 | .hljs-attribute { 59 | color: #000080; 60 | font-weight: normal; 61 | } 62 | 63 | .hljs-regexp, 64 | .hljs-link { 65 | color: #009926; 66 | } 67 | 68 | .hljs-symbol, 69 | .hljs-bullet { 70 | color: #990073; 71 | } 72 | 73 | .hljs-built_in, 74 | .hljs-builtin-name { 75 | color: #0086b3; 76 | } 77 | 78 | .hljs-meta { 79 | color: #999; 80 | font-weight: bold; 81 | } 82 | 83 | .hljs-deletion { 84 | background: #fdd; 85 | } 86 | 87 | .hljs-addition { 88 | background: #dfd; 89 | } 90 | 91 | .hljs-emphasis { 92 | font-style: italic; 93 | } 94 | 95 | .hljs-strong { 96 | font-weight: bold; 97 | } 98 | -------------------------------------------------------------------------------- /docs/images/domain-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/docs/images/domain-model.png -------------------------------------------------------------------------------- /docs/images/maintenance-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/logicblocks/salutem/b22b7d8348366255d03a6e4eee44473d333d6958/docs/images/maintenance-pipeline.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 3 | Salutem 0.1.8

Salutem 0.1.8

Released under the MIT License

Aggregate project for all salutem modules.

Installation

To install, add the following dependency to your project or build file:

[io.logicblocks/salutem "0.1.8"]

Topics

Namespaces

salutem.check-fns.data-source.core

Provides a data source check function for salutem.

4 |

Public variables and functions:

salutem.check-fns.http-endpoint.core

Provides an HTTP endpoint check function for salutem.

5 |

Public variables and functions:

salutem.core.checks

Provides constructors, predicates and evaluation functions for checks.

7 |

salutem.core.maintenance

Provides an asynchronous maintenance pipeline for maintaining up-to-date results for the checks in a registry.

8 |

salutem.core.registry

Provides constructors, query functions and resolution functions for registries.

9 |

salutem.core.results

Provides constructors and predicates for check results.

10 |

salutem.core.time

Provides time utilities for use in check definitions and the maintenance pipeline.

11 |

Public variables and functions:

-------------------------------------------------------------------------------- /docs/js/highlight.min.js: -------------------------------------------------------------------------------- 1 | /*! highlight.js v9.6.0 | BSD3 License | git.io/hljslicense */ 2 | !function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/[&<>]/gm,function(e){return I[e]})}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return R(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||R(i))return i}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){l+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===s);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return l+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):E(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"===e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function l(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function g(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function h(e,n,t,r){var a=r?"":y.classPrefix,i='',i+n+o}function p(){var e,t,r,a;if(!E.k)return n(B);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(B);r;)a+=n(B.substr(t,r.index-t)),e=g(E,r),e?(M+=e[1],a+=h(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(B);return a+n(B.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!x[E.sL])return n(B);var t=e?l(E.sL,B,!0,L[E.sL]):f(B,E.sL.length?E.sL:void 0);return E.r>0&&(M+=t.r),e&&(L[E.sL]=t.top),h(t.language,t.value,!1,!0)}function b(){k+=null!=E.sL?d():p(),B=""}function v(e){k+=e.cN?h(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(B+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?B+=n:(t.eB&&(B+=n),b(),t.rB||t.eB||(B=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?B+=n:(a.rE||a.eE||(B+=n),b(),a.eE&&(B=n));do E.cN&&(k+=C),E.skip||(M+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return B+=n,n.length||1}var N=R(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var w,E=i||N,L={},k="";for(w=E;w!==N;w=w.parent)w.cN&&(k=h(w.cN,"",!0)+k);var B="",M=0;try{for(var I,j,O=0;;){if(E.t.lastIndex=O,I=E.t.exec(t),!I)break;j=m(t.substr(O,I.index-O),I[0]),O=I.index+j}for(m(t.substr(O)),w=E;w.parent;w=w.parent)w.cN&&(k+=C);return{r:M,value:k,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function f(e,t){t=t||y.languages||E(x);var r={r:0,value:n(e)},a=r;return t.filter(R).forEach(function(n){var t=l(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function g(e){return y.tabReplace||y.useBR?e.replace(M,function(e,n){return y.useBR&&"\n"===e?"
":y.tabReplace?n.replace(/\t/g,y.tabReplace):void 0}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n,t,r,o,s,p=i(e);a(p)||(y.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,s=n.textContent,r=p?l(p,s,!0):f(s),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),s)),r.value=g(r.value),e.innerHTML=r.value,e.className=h(e.className,p,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function d(e){y=o(y,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");w.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=x[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function N(){return E(x)}function R(e){return e=(e||"").toLowerCase(),x[e]||x[L[e]]}var w=[],E=Object.keys,x={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",y={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},I={"&":"&","<":"<",">":">"};return e.highlight=l,e.highlightAuto=f,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=R,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("clojure",function(e){var t={"builtin-name":"def defonce cond apply if-not if-let if not not= = < > <= >= == + / * - rem quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last drop-while while intern condp case reduced cycle split-at split-with repeat replicate iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter monitor-exit defmacro defn defn- macroexpand macroexpand-1 for dosync and or when when-not when-let comp juxt partial sequence memoize constantly complement identity assert peek pop doto proxy defstruct first rest cons defprotocol cast coll deftype defrecord last butlast sigs reify second ffirst fnext nfirst nnext defmulti defmethod meta with-meta ns in-ns create-ns import refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize"},r="a-zA-Z_\\-!.?+*=<>&#'",n="["+r+"]["+r+"0-9/;:]*",a="[-+]?\\d+(\\.\\d+)?",o={b:n,r:0},s={cN:"number",b:a,r:0},i=e.inherit(e.QSM,{i:null}),c=e.C(";","$",{r:0}),d={cN:"literal",b:/\b(true|false|nil)\b/},l={b:"[\\[\\{]",e:"[\\]\\}]"},m={cN:"comment",b:"\\^"+n},p=e.C("\\^\\{","\\}"),u={cN:"symbol",b:"[:]{1,2}"+n},f={b:"\\(",e:"\\)"},h={eW:!0,r:0},y={k:t,l:n,cN:"name",b:n,starts:h},b=[f,i,m,p,c,u,l,s,d,o];return f.c=[e.C("comment",""),y,h],h.c=b,l.c=b,{aliases:["clj"],i:/\S/,c:[f,i,m,p,c,u,l,s,d]}});hljs.registerLanguage("clojure-repl",function(e){return{c:[{cN:"meta",b:/^([\w.-]+|\s*#_)=>/,starts:{e:/$/,sL:"clojure"}}]}}); -------------------------------------------------------------------------------- /docs/js/page_effects.js: -------------------------------------------------------------------------------- 1 | function visibleInParent(element) { 2 | var position = $(element).position().top 3 | return position > -50 && position < ($(element).offsetParent().height() - 50) 4 | } 5 | 6 | function hasFragment(link, fragment) { 7 | return $(link).attr("href").indexOf("#" + fragment) != -1 8 | } 9 | 10 | function findLinkByFragment(elements, fragment) { 11 | return $(elements).filter(function(i, e) { return hasFragment(e, fragment)}).first() 12 | } 13 | 14 | function scrollToCurrentVarLink(elements) { 15 | var elements = $(elements); 16 | var parent = elements.offsetParent(); 17 | 18 | if (elements.length == 0) return; 19 | 20 | var top = elements.first().position().top; 21 | var bottom = elements.last().position().top + elements.last().height(); 22 | 23 | if (top >= 0 && bottom <= parent.height()) return; 24 | 25 | if (top < 0) { 26 | parent.scrollTop(parent.scrollTop() + top); 27 | } 28 | else if (bottom > parent.height()) { 29 | parent.scrollTop(parent.scrollTop() + bottom - parent.height()); 30 | } 31 | } 32 | 33 | function setCurrentVarLink() { 34 | $('.secondary a').parent().removeClass('current') 35 | $('.anchor'). 36 | filter(function(index) { return visibleInParent(this) }). 37 | each(function(index, element) { 38 | findLinkByFragment(".secondary a", element.id). 39 | parent(). 40 | addClass('current') 41 | }); 42 | scrollToCurrentVarLink('.secondary .current'); 43 | } 44 | 45 | var hasStorage = (function() { try { return localStorage.getItem } catch(e) {} }()) 46 | 47 | function scrollPositionId(element) { 48 | var directory = window.location.href.replace(/[^\/]+\.html$/, '') 49 | return 'scroll::' + $(element).attr('id') + '::' + directory 50 | } 51 | 52 | function storeScrollPosition(element) { 53 | if (!hasStorage) return; 54 | localStorage.setItem(scrollPositionId(element) + "::x", $(element).scrollLeft()) 55 | localStorage.setItem(scrollPositionId(element) + "::y", $(element).scrollTop()) 56 | } 57 | 58 | function recallScrollPosition(element) { 59 | if (!hasStorage) return; 60 | $(element).scrollLeft(localStorage.getItem(scrollPositionId(element) + "::x")) 61 | $(element).scrollTop(localStorage.getItem(scrollPositionId(element) + "::y")) 62 | } 63 | 64 | function persistScrollPosition(element) { 65 | recallScrollPosition(element) 66 | $(element).scroll(function() { storeScrollPosition(element) }) 67 | } 68 | 69 | function sidebarContentWidth(element) { 70 | var widths = $(element).find('.inner').map(function() { return $(this).innerWidth() }) 71 | return Math.max.apply(Math, widths) 72 | } 73 | 74 | function calculateSize(width, snap, margin, minimum) { 75 | if (width == 0) { 76 | return 0 77 | } 78 | else { 79 | return Math.max(minimum, (Math.ceil(width / snap) * snap) + (margin * 2)) 80 | } 81 | } 82 | 83 | function resizeSidebars() { 84 | var primaryWidth = sidebarContentWidth('.primary') 85 | var secondaryWidth = 0 86 | 87 | if ($('.secondary').length != 0) { 88 | secondaryWidth = sidebarContentWidth('.secondary') 89 | } 90 | 91 | // snap to grid 92 | primaryWidth = calculateSize(primaryWidth, 32, 13, 160) 93 | secondaryWidth = calculateSize(secondaryWidth, 32, 13, 160) 94 | 95 | $('.primary').css('width', primaryWidth) 96 | $('.secondary').css('width', secondaryWidth).css('left', primaryWidth + 1) 97 | 98 | if (secondaryWidth > 0) { 99 | $('#content').css('left', primaryWidth + secondaryWidth + 2) 100 | } 101 | else { 102 | $('#content').css('left', primaryWidth + 1) 103 | } 104 | } 105 | 106 | $(window).ready(resizeSidebars) 107 | $(window).ready(setCurrentVarLink) 108 | $(window).ready(function() { persistScrollPosition('.primary')}) 109 | $(window).ready(function() { 110 | $('#content').scroll(setCurrentVarLink) 111 | $(window).resize(setCurrentVarLink) 112 | }) 113 | -------------------------------------------------------------------------------- /docs/salutem.check-fns.data-source.core.html: -------------------------------------------------------------------------------- 1 | 3 | salutem.check-fns.data-source.core documentation

salutem.check-fns.data-source.core

Provides a data source check function for salutem.

4 |

Packaged in a separate module, salutem.check-fns.data-source versioned in lock step with salutem.core.

5 |

data-source-check-fn

(data-source-check-fn data-source)(data-source-check-fn data-source {:keys [query-sql-params query-timeout query-opts query-results-result-fn failure-reason-fn exception-result-fn]})

Returns a check function for the provided javax.sql.DataSource.

6 |

Accepts the following options in the option map:

7 |
    8 |
  • :query-sql-params: an SQL parameter vector (as defined in next.jdbc) or a function of context that will return an SQL parameter vector containing the health check query to execute against the data source, defaults to ["SELECT 1 AS up;"].
  • 9 |
  • :query-timeout: a salutem.core/duration or a function of context that will return a salutem.core/duration representing the amount of time to wait for the query to finish before considering it failed; defaults to 5 seconds.
  • 10 |
  • :query-opts: additional options (as defined in next.jdbc) or a function of context that will return additional options to pass at query execution time; by default, includes a builder function for rows that returns unqualified kebab-cased maps; additional options are merged into the default option map.
  • 11 |
  • :query-results-result-fn: a function, of context and the results of the query, used to produce a result for the check; by default, a healthy result is returned, including the contents of the first record from the results.
  • 12 |
  • :failure-reason-fn: a function, of context and an exception, to determine the reason for a failure; by default uses failure-reason.
  • 13 |
  • :exception-result-fn: a function, of context and an exception, used to produce a result for the check in the case that an exception is thrown; by default, an unhealthy result is returned including a :salutem/reason entry with the reason derived by :failure-reason-fn and a :salutem/exception entry containing the thrown exception.
  • 14 |
15 |

If the returned check function is invoked with a context map including a :logger key with a cartus.core/Logger value, the check function will emit a number of log events whilst executing.

16 |

failure-reason

(failure-reason exception)

Determines the failure reason associated with an exception.

17 |

This failure reason function, the default used by the check function, uses a reason of :threw-exception for all exceptions other than a java.sql.SQLTimeoutException for which the reason is :timed-out.

18 |

In the case that this default behaviour is insufficient, an alternative failure reason function can be passed to data-source-check-fn using the :failure-reason-fn option.

19 |
-------------------------------------------------------------------------------- /docs/salutem.check-fns.http-endpoint.core.html: -------------------------------------------------------------------------------- 1 | 3 | salutem.check-fns.http-endpoint.core documentation

salutem.check-fns.http-endpoint.core

Provides an HTTP endpoint check function for salutem.

4 |

Packaged in a separate module, salutem.check-fns.http-endpoint versioned in lock step with salutem.core.

5 |

failure-reason

(failure-reason exception)

Determines the failure reason associated with an exception.

6 |

This failure reason function, the default used by the check function, uses a reason of :threw-exception for all exceptions other than:

7 | 12 |

for which the reason is :timed-out.

13 |

In the case that this default behaviour is insufficient, an alternative failure reason function can be passed to http-endpoint-check-fn using the :failure-reason-fn option.

14 |

http-endpoint-check-fn

(http-endpoint-check-fn url)(http-endpoint-check-fn url {:keys [method body headers query-params opts connection-request-timeout connection-timeout socket-timeout successful-response-fn response-result-fn failure-reason-fn exception-result-fn]})

Returns a check function for the HTTP endpoint identified by the provided URL.

15 |

Accepts the following options in the option map:

16 |
    17 |
  • :method: a keyword representing the method used to check the endpoint (one of :get, :head, :post, :put, :delete, :options, :copy, :move or :patch) or a function of context that will return such a keyword; defaults to :get.
  • 18 |
  • :body: an object representing the body sent to the endpoint on check execution (supporting anything clj-http will accept) or a function of context that will return such an object; defaults to nil.
  • 19 |
  • :headers: a map of headers to be sent to the endpoint on check execution (as supported by clj-http) or a function of context that will return such a map; defaults to nil.
  • 20 |
  • :query-params: a map of query parameters to be sent to the endpoint on check execution (as supported by clj-http) or a function of context that will return such a map; defaults to nil.
  • 21 |
  • :opts: a map of additional query options (as supported by clj-http) or a function of context that will return such a map; defaults to {:throw-exceptions false} since we want response success to be deduced by the :response-result-fn rather than treating unsuccessful statuses as exceptions; note that any timeouts passed in this query options map are ignored and should be set using :connection-request-timeout, :connection-timeout and :socket-timeout.
  • 22 |
  • :connection-request-timeout: the salutem.core/duration to wait when obtaining a connection from the connection manager before considering the request failed; defaults to 5 seconds.
  • 23 |
  • :connection-timeout: the salutem.core/duration to wait when establishing an HTTP connection before considering the request failed; defaults to 5 seconds.
  • 24 |
  • :socket-timeout: the salutem.core/duration to wait while streaming response data since the last data was received before considering the request failed; defaults to 5 seconds.
  • 25 |
  • :successful-response-fn: a function of context and the response from a request to the endpoint, returning true if the response was successful, false otherwise; by default uses successful?.
  • 26 |
  • :response-result-fn: a function, of context and the response from a request to the endpoint, used to produce a result for the check; by default, a healthy result is returned if the response is successful according to :successful-response-fn, otherwise an unhealthy result is returned.
  • 27 |
  • :failure-reason-fn: a function, of context and an exception, to determine the reason for a failure; by default uses failure-reason.
  • 28 |
  • :exception-result-fn: a function, of context and an exception, used to produce a result for the check in the case that an exception is thrown; by default, an unhealthy result is returned including a :salutem/reason entry with the reason derived by :failure-reason-fn and a :salutem/exception entry containing the thrown exception.
  • 29 |
30 |

Additionally, if the URL parameter is instead a function, it will be called with the context map at check execution time in order to obtain the endpoint URL.

31 |

If the returned check function is invoked with a context map including a :logger key with a cartus.core/Logger value, the check function will emit a number of log events whilst executing.

32 |

successful?

(successful? {:keys [status]})

Returns true if the provided response has a successful status, false otherwise.

33 |

This response success function, the default used by the check function, treats status codes of 200, 201, 202, 203, 204, 205, 206, 207, 300, 301, 302, 303, 304, 307 and 308 as successful.

34 |

In the case that this default behaviour is insufficient, an alternative response success function can be passed to http-endpoint-check-fn using the :successful-response-fn option.

35 |
-------------------------------------------------------------------------------- /docs/salutem.core.registry.html: -------------------------------------------------------------------------------- 1 | 3 | salutem.core.registry documentation

salutem.core.registry

Provides constructors, query functions and resolution functions for registries.

4 |

all-checks

(all-checks registry)

Returns the set of checks present in the registry.

5 |

check-names

(check-names registry)

Returns the set of check names present in the registry.

6 |

empty-registry

(empty-registry)

Constructs an empty registry which can be populated using with-check and with-cached-result.

7 |

find-cached-result

(find-cached-result registry check-name)

Finds the cached result for the check with the given name in the registry. Returns nil if no result can be found or if the check does not exist.

8 |

find-check

(find-check registry check-name)

Finds the check with the given name in the registry. Returns nil if no check can be found.

9 |

outdated-checks

(outdated-checks registry)

Returns the set of checks that are currently outdated in the registry based on the type of the check and the cached results available.

10 |

See salutem.results/outdated? for details on which it means for a check to be outdated.

11 |

resolve-check

(resolve-check registry check-name)(resolve-check registry check-name context)(resolve-check registry check-name context callback-fn)

Resolves a result for the check of the given name in the registry.

12 |

If the check is a background check and there is a cached result available, it is returned. If no cached result is available, the check is evaluated in order to obtain a result to return.

13 |

If the check is a realtime check, it is always evaluated in order to obtain a result to return and caching is not used.

14 |

Optionally takes a context map containing arbitrary context required by the check in order to run and passed to the check function as the first argument.

15 |

By default, the check is resolved synchronously. If a callback function is provided, the function starts resolution asynchronously, returns immediately and invokes the callback function with the result once available.

16 |

resolve-checks

(resolve-checks registry)(resolve-checks registry context)(resolve-checks registry context callback-fn)

Resolves all checks in the registry, returning a map of check names to results.

17 |

Checks requiring re-evaluation are evaluated in parallel such that this function should take about as long as the slowest check (assuming IO is the dominant blocker).

18 |

Optionally takes a context map containing arbitrary context required by checks in order to run and passed to the check functions as the first argument.

19 |

By default, the checks are resolved synchronously. If a callback function is provided, the function starts resolution asynchronously, returns immediately and invokes the callback function with the results once available.

20 |

See resolve-check for details on how each check is resolved.

21 |

with-cached-result

(with-cached-result registry check-name result)

Adds the result for the check with the given name to the registry, returning a new registry.

22 |

with-check

(with-check registry check)

Adds the check to the registry, returning a new registry.

23 |
-------------------------------------------------------------------------------- /docs/salutem.core.results.html: -------------------------------------------------------------------------------- 1 | 3 | salutem.core.results documentation

salutem.core.results

Provides constructors and predicates for check results.

4 |

healthy

(healthy)(healthy extra-data)

Constructs a healthy result.

5 |

The optional map of extra data is stored with the result for future use. Unless overridden in the extra data map, an :salutem/evaluated-at field is added to the result, set to the current date time in the system default time zone.

6 |

healthy?

(healthy? result)

Returns true if the result has a :healthy status, false otherwise.

7 |

outdated?

(outdated? result check)(outdated? result check relative-to)

Returns true if the result of the check is outdated, false otherwise.

8 |

For a realtime check, a result is always considered outdated.

9 |

For a background check, a result is considered outdated if the time to re-evaluation of the check has passed, i.e., if its evaluation date time is before the current date time minus the check’s time to re-evaluation.

10 |

If relative-to is provided, the calculation is performed relative to that date time rather than to the current date time.

11 |

prepend

(prepend result extra-data)

Adds each entry from the provided map of extra data to the result if no entry already exists in the result for the key.

12 |

result

(result status)(result status {:keys [salutem/evaluated-at], :or {evaluated-at (t/now)}, :as extra-data})

Constructs a result with the provided status.

13 |

The optional map of extra data is stored with the result for future use. Unless overridden in the extra data map, an :salutem/evaluated-at field is added to the result, set to the current date time in the system default time zone.

14 |

status

(status result)

Returns the status of the provided result.

15 |

unhealthy

(unhealthy)(unhealthy extra-date)

Constructs an unhealthy result.

16 |

The optional map of extra data is stored with the result for future use. Unless overridden in the extra data map, an :salutem/evaluated-at field is added to the result, set to the current date time in the system default time zone.

17 |

unhealthy?

(unhealthy? result)

Returns true if the result has an :unhealthy status, false otherwise.

18 |
-------------------------------------------------------------------------------- /docs/salutem.core.time.html: -------------------------------------------------------------------------------- 1 | 3 | salutem.core.time documentation

salutem.core.time

Provides time utilities for use in check definitions and the maintenance pipeline.

4 |

duration

(duration amount unit)

Constructs an object representing a duration of time.

5 |

This object is used to specify, for example, the time to re-evaluation and timeout on a check or the interval passed to a maintenance pipeline.

6 |

Takes an amount and a unit:

7 |
    8 |
  • amount is the length of the duration, measured in terms of the unit
  • 9 |
  • unit is one of :nanos, :micros, :millis, :seconds, :minutes, :hours, :half-days, :days, :weeks, :months, :years, :decades, :centuries, :millennia, :eras or :forever
  • 10 |
11 |

Note: internally, this constructs a java.time.Duration and is merely a convenience function. As such, a java.time.Duration can be passed directly wherever this function would be used.

12 |
-------------------------------------------------------------------------------- /go: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -n "$GO_DEBUG" ] && set -x 4 | set -e 5 | 6 | project_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 7 | 8 | verbose="no" 9 | offline="no" 10 | skip_checks="no" 11 | 12 | missing_dependency="no" 13 | 14 | [ -n "$GO_DEBUG" ] && verbose="yes" 15 | [ -n "$GO_SKIP_CHECKS" ] && skip_checks="yes" 16 | [ -n "$GO_OFFLINE" ] && offline="yes" 17 | 18 | function loose_version() { 19 | local version="$1" 20 | 21 | IFS="." read -r -a version_parts <<<"$version" 22 | 23 | echo "${version_parts[0]}.${version_parts[1]}" 24 | } 25 | 26 | ruby_full_version="$(cat "$project_dir"/.ruby-version)" 27 | ruby_loose_version="$(loose_version "$ruby_full_version")" 28 | 29 | if [[ "$skip_checks" == "no" ]]; then 30 | echo "Checking for system dependencies." 31 | if ! type ruby >/dev/null 2>&1 || ! ruby -v | grep -q "$ruby_loose_version"; then 32 | echo "This codebase requires Ruby $ruby_loose_version." 33 | missing_dependency="yes" 34 | fi 35 | 36 | if ! type bundler >/dev/null 2>&1; then 37 | echo "This codebase requires Bundler." 38 | missing_dependency="yes" 39 | fi 40 | 41 | if [[ "$missing_dependency" = "yes" ]]; then 42 | echo "Please install missing dependencies to continue." 43 | exit 1 44 | fi 45 | 46 | echo "All system dependencies present. Continuing." 47 | fi 48 | 49 | if [[ "$offline" == "no" ]]; then 50 | echo "Installing bundler." 51 | if [[ "$verbose" == "yes" ]]; then 52 | gem install --no-document bundler 53 | else 54 | gem install --no-document bundler >/dev/null 55 | fi 56 | 57 | echo "Installing ruby dependencies." 58 | if [[ "$verbose" == "yes" ]]; then 59 | bundle install 60 | else 61 | bundle install >/dev/null 62 | fi 63 | fi 64 | 65 | echo "Starting rake." 66 | if [[ "$verbose" == "yes" ]]; then 67 | time bundle exec rake --verbose "$@" 68 | else 69 | time bundle exec rake "$@" 70 | fi 71 | -------------------------------------------------------------------------------- /parent/project.clj: -------------------------------------------------------------------------------- 1 | (defproject io.logicblocks/salutem.parent "0.1.9-RC4" 2 | :scm {:dir "." 3 | :name "git" 4 | :url "https://github.com/logicblocks/salutem"} 5 | 6 | :url "https://github.com/logicblocks/salutem" 7 | 8 | :license 9 | {:name "The MIT License" 10 | :url "https://opensource.org/licenses/MIT"} 11 | 12 | :plugins [[io.logicblocks/lein-interpolate "0.1.1-RC3"] 13 | [jonase/eastwood "1.4.2"] 14 | [lein-ancient "0.7.0"] 15 | [lein-bikeshed "0.5.2"] 16 | [lein-cljfmt "0.9.2"] 17 | [lein-cloverage "1.2.4"] 18 | [lein-cprint "1.3.3"] 19 | [lein-eftest "0.6.0"] 20 | [lein-kibit "0.1.8"] 21 | [lein-shell "0.5.0"] 22 | [fipp "0.6.26"]] 23 | 24 | :deploy-repositories 25 | {"releases" {:url "https://repo.clojars.org" :creds :gpg} 26 | "snapshots" {:url "https://repo.clojars.org" :creds :gpg}} 27 | 28 | :managed-dependencies 29 | [[org.clojure/clojure "1.11.1"] 30 | [org.clojure/tools.trace "0.7.11"] 31 | [org.clojure/core.async "1.6.673"] 32 | 33 | [io.logicblocks/cartus.core "0.1.18"] 34 | [io.logicblocks/cartus.test "0.1.18"] 35 | [io.logicblocks/cartus.null "0.1.18"] 36 | 37 | [io.logicblocks/salutem.core :project/version] 38 | [io.logicblocks/salutem.check-fns :project/version] 39 | [io.logicblocks/salutem.check-fns.data-source :project/version] 40 | [io.logicblocks/salutem.check-fns.http-endpoint :project/version] 41 | 42 | [tick "0.7.5"] 43 | 44 | [nrepl "1.1.0"] 45 | 46 | [eftest "0.6.0"] 47 | [tortue/spy "2.14.0"] 48 | 49 | [vlaaad/reveal "1.3.280"] 50 | 51 | [org.jooq/jooq "3.15.3"] 52 | [com.impossibl.pgjdbc-ng/pgjdbc-ng "0.8.9"] 53 | 54 | [com.github.seancorfield/next.jdbc "1.3.894"] 55 | 56 | [clj-http "3.12.3"] 57 | 58 | [clj-http-fake "1.0.4"] 59 | [kelveden/clj-wiremock "1.8.0"] 60 | 61 | [org.slf4j/slf4j-nop "2.0.7"]] 62 | 63 | :profiles 64 | {:parent-shared 65 | ^{:pom-scope :test} 66 | {:dependencies [[org.clojure/clojure] 67 | [org.clojure/tools.trace] 68 | 69 | [io.logicblocks/cartus.test] 70 | 71 | [nrepl] 72 | 73 | [eftest]]} 74 | 75 | :parent-reveal 76 | [:parent-shared 77 | {:dependencies [[vlaaad/reveal]] 78 | :repl-options {:nrepl-middleware [vlaaad.reveal.nrepl/middleware]} 79 | :jvm-opts ["-Dvlaaad.reveal.prefs={:theme :light :font-family \"FiraCode Nerd Font Mono\" :font-size 13}"]}] 80 | 81 | :parent-dev 82 | ^{:pom-scope :test} 83 | [:parent-shared 84 | {:source-paths ["dev"]}] 85 | 86 | :parent-unit 87 | [:parent-shared {:test-paths ^:replace ["test/shared" 88 | "test/unit"]}] 89 | 90 | :parent-integration 91 | [:parent-shared {:test-paths ^:replace ["test/shared" 92 | "test/integration"]}] 93 | 94 | :parent-performance 95 | [:parent-shared {:test-paths ^:replace ["test/shared" 96 | "test/performance"]}]} 97 | 98 | :source-paths [] 99 | :test-paths [] 100 | :resource-paths [] 101 | 102 | :cloverage 103 | {:ns-exclude-regex [#"^user"]} 104 | 105 | :bikeshed 106 | {:name-collisions false 107 | :long-lines false} 108 | 109 | :cljfmt 110 | {:indents {#".*" [[:inner 0]] 111 | defrecord [[:block 1] [:inner 1]] 112 | deftype [[:block 1] [:inner 1]]}} 113 | 114 | :eastwood 115 | {:config-files 116 | [~(str (System/getProperty "user.dir") "/config/linter.clj")]}) 117 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject io.logicblocks/salutem "0.1.9-RC4" 2 | :description "Aggregate project for all salutem modules." 3 | 4 | :parent-project {:path "parent/project.clj" 5 | :inherit [:scm 6 | :url 7 | :license 8 | :plugins 9 | [:profiles :parent-shared] 10 | :deploy-repositories 11 | :managed-dependencies]} 12 | 13 | :plugins [[io.logicblocks/lein-interpolate "0.1.1-RC3"] 14 | [lein-parent "0.3.9"] 15 | [lein-sub "0.3.0"] 16 | [lein-changelog "0.3.2"] 17 | [lein-codox "0.10.8"]] 18 | 19 | :sub ["parent" 20 | "core" 21 | "check-fns/data-source" 22 | "check-fns/http-endpoint" 23 | "check-fns" 24 | "."] 25 | 26 | :dependencies [[io.logicblocks/salutem.core] 27 | [io.logicblocks/salutem.check-fns]] 28 | 29 | :profiles 30 | {:unit 31 | {:aliases {"eftest" 32 | ["sub" 33 | "-s" "core:check-fns/data-source:check-fns/http-endpoint" 34 | "with-profile" "unit" 35 | "eftest"]}} 36 | 37 | :integration 38 | {:aliases {"eftest" 39 | ["sub" 40 | "-s" "check-fns/data-source:check-fns/http-endpoint" 41 | "with-profile" "integration" 42 | "eftest"]}} 43 | 44 | :performance 45 | {:aliases {"eftest" 46 | ["sub" 47 | "-s" "core" 48 | "with-profile" "performance" 49 | "eftest"]}} 50 | 51 | :codox 52 | [:parent-shared 53 | {:dependencies [[io.logicblocks/salutem.core :project/version] 54 | 55 | [org.clojure/core.async] 56 | 57 | [io.logicblocks/cartus.core] 58 | [io.logicblocks/cartus.null] 59 | 60 | [clj-http] 61 | 62 | [com.github.seancorfield/next.jdbc] 63 | 64 | [tick]] 65 | 66 | :source-paths ["core/src" 67 | "check-fns/data-source/src" 68 | "check-fns/http-endpoint/src"]}] 69 | 70 | :prerelease 71 | {:release-tasks 72 | [ 73 | ["vcs" "assert-committed"] 74 | ["sub" "change" "version" "leiningen.release/bump-version" "rc"] 75 | ["sub" "change" "version" "leiningen.release/bump-version" "release"] 76 | ["vcs" "commit" "Pre-release version %s [skip ci]"] 77 | ["vcs" "tag"] 78 | ["sub" "-s" "core:check-fns/data-source:check-fns/http-endpoint:check-fns:." "deploy"]]} 79 | 80 | :release 81 | {:release-tasks 82 | [["vcs" "assert-committed"] 83 | ["sub" "change" "version" "leiningen.release/bump-version" "release"] 84 | ["sub" "-s" "core:check-fns/data-source:check-fns/http-endpoint:check-fns:." "install"] 85 | ["changelog" "release"] 86 | ["shell" "sed" "-E" "-i.bak" "s/salutem\\.(.+) \"[0-9]+\\.[0-9]+\\.[0-9]+\"/salutem.\\\\1 \"${:version}\"/g" "README.md"] 87 | ["shell" "rm" "-f" "README.md.bak"] 88 | ["shell" "sed" "-E" "-i.bak" "s/salutem\\.(.+) \"[0-9]+\\.[0-9]+\\.[0-9]+\"/salutem.\\\\1 \"${:version}\"/g" "docs/01-getting-started.md"] 89 | ["shell" "sed" "-E" "-i.bak" "s/salutem\\.(.+) \"[0-9]+\\.[0-9]+\\.[0-9]+\"/salutem.\\\\1 \"${:version}\"/g" "docs/02-check-functions.md"] 90 | ["shell" "rm" "-f" "docs/01-getting-started.md.bak"] 91 | ["shell" "rm" "-f" "docs/02-check-functions.md.bak"] 92 | ["codox"] 93 | ["shell" "git" "add" "."] 94 | ["vcs" "commit" "Release version %s [skip ci]"] 95 | ["vcs" "tag"] 96 | ["sub" "-s" "core:check-fns/data-source:check-fns/http-endpoint:check-fns:." "deploy"] 97 | ["sub" "change" "version" "leiningen.release/bump-version" "patch"] 98 | ["sub" "change" "version" "leiningen.release/bump-version" "rc"] 99 | ["sub" "change" "version" "leiningen.release/bump-version" "release"] 100 | ["vcs" "commit" "Pre-release version %s [skip ci]"] 101 | ["vcs" "tag"] 102 | ["vcs" "push"]]}} 103 | 104 | :source-paths [] 105 | :test-paths [] 106 | :resource-paths [] 107 | 108 | :codox 109 | {:namespaces [#"^salutem\."] 110 | :metadata {:doc/format :markdown} 111 | :output-path "docs" 112 | :doc-paths ["docs"] 113 | :source-uri "https://github.com/logicblocks/salutem/blob/{version}/{filepath}#L{line}"} 114 | 115 | :aliases {"install" 116 | ["do" 117 | ["sub" 118 | "-s" "core:check-fns/data-source:check-fns/http-endpoint:check-fns" 119 | "install"] 120 | ["install"]] 121 | 122 | "eastwood" 123 | ["sub" 124 | "-s" "core:check-fns/data-source:check-fns/http-endpoint" 125 | "eastwood"] 126 | 127 | "cljfmt" 128 | ["sub" 129 | "-s" "core:check-fns/data-source:check-fns/http-endpoint" 130 | "cljfmt"] 131 | 132 | "kibit" 133 | ["sub" 134 | "-s" "core:check-fns/data-source:check-fns/http-endpoint" 135 | "kibit"] 136 | 137 | "check" 138 | ["sub" 139 | "-s" "core:check-fns/data-source:check-fns/http-endpoint" 140 | "check"] 141 | 142 | "bikeshed" 143 | ["sub" 144 | "-s" "core:check-fns/data-source:check-fns/http-endpoint" 145 | "bikeshed"]}) 146 | -------------------------------------------------------------------------------- /scripts/ci/common/configure-clojars.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -n "$DEBUG" ] && set -x 4 | set -e 5 | set -o pipefail 6 | 7 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 8 | PROJECT_DIR="$( cd "$SCRIPT_DIR/../../.." && pwd )" 9 | 10 | cd "$PROJECT_DIR" 11 | 12 | mkdir -p ~/.lein 13 | cp config/secrets/clojars/credentials.clj.gpg ~/.lein/credentials.clj.gpg 14 | chmod 0600 ~/.lein/credentials.clj.gpg 15 | -------------------------------------------------------------------------------- /scripts/ci/common/configure-git.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -n "$DEBUG" ] && set -x 4 | set -e 5 | set -o pipefail 6 | 7 | git crypt unlock 8 | 9 | KEY_UID="$(cat config/secrets/ci/gpg.uid)" 10 | KEY_ID="$(gpg --list-keys --with-colons | \ 11 | grep -C 1 "${KEY_UID}" | \ 12 | head -n 1 | \ 13 | cut -d ':' -f 10)" 14 | 15 | git config --global user.email "circleci@logicblocks.io" 16 | git config --global user.name "Circle CI" 17 | git config --global user.signingkey "${KEY_ID}" 18 | -------------------------------------------------------------------------------- /scripts/ci/common/install-git-crypt.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -n "$DEBUG" ] && set -x 4 | set -e 5 | set -o pipefail 6 | 7 | apt-get update 8 | apt-get install -y --no-install-recommends \ 9 | git \ 10 | ssh \ 11 | git-crypt 12 | -------------------------------------------------------------------------------- /scripts/ci/common/install-gpg-key.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -n "$DEBUG" ] && set -x 4 | set -e 5 | set -o pipefail 6 | 7 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 8 | PROJECT_DIR="$( cd "$SCRIPT_DIR/../../.." && pwd )" 9 | 10 | cd "$PROJECT_DIR" 11 | 12 | set +e 13 | openssl version 14 | openssl aes-256-cbc \ 15 | -d \ 16 | -md sha1 \ 17 | -in ./.circleci/gpg.private.enc \ 18 | -k "${ENCRYPTION_PASSPHRASE}" | gpg --import - 19 | set -e 20 | -------------------------------------------------------------------------------- /scripts/ci/common/install-openjdk.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -n "$DEBUG" ] && set -x 4 | set -e 5 | set -o pipefail 6 | 7 | apt-get update 8 | apt-get install -y --no-install-recommends openjdk-11-jdk 9 | -------------------------------------------------------------------------------- /scripts/ci/common/upgrade-gpg.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -n "$DEBUG" ] && set -x 4 | set -e 5 | set -o pipefail 6 | 7 | apt update 8 | apt -y --no-install-recommends install gpg 9 | -------------------------------------------------------------------------------- /scripts/ci/steps/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -n "$DEBUG" ] && set -x 4 | set -e 5 | set -o pipefail 6 | 7 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 8 | PROJECT_DIR="$( cd "$SCRIPT_DIR/../../.." && pwd )" 9 | 10 | cd "$PROJECT_DIR" 11 | 12 | ./go build:code:check library:initialise library:check 13 | -------------------------------------------------------------------------------- /scripts/ci/steps/merge-pull-request.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -n "$DEBUG" ] && set -x 4 | set -e 5 | set -o pipefail 6 | 7 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 8 | PROJECT_DIR="$( cd "$SCRIPT_DIR/../../.." && pwd )" 9 | 10 | cd "$PROJECT_DIR" 11 | 12 | git-crypt unlock 13 | 14 | CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) 15 | 16 | ./go github:pull_requests:merge["$CURRENT_BRANCH","%s [skip ci]"] 17 | -------------------------------------------------------------------------------- /scripts/ci/steps/prerelease.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -n "$DEBUG" ] && set -x 4 | set -e 5 | set -o pipefail 6 | 7 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 8 | PROJECT_DIR="$( cd "$SCRIPT_DIR/../../.." && pwd )" 9 | 10 | cd "$PROJECT_DIR" 11 | 12 | git crypt unlock 13 | 14 | ./go library:initialise library:publish:prerelease 15 | 16 | git status 17 | git push 18 | -------------------------------------------------------------------------------- /scripts/ci/steps/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -n "$DEBUG" ] && set -x 4 | set -e 5 | set -o pipefail 6 | 7 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 8 | PROJECT_DIR="$( cd "$SCRIPT_DIR/../../.." && pwd )" 9 | 10 | cd "$PROJECT_DIR" 11 | 12 | git crypt unlock 13 | 14 | git pull 15 | 16 | ./go library:initialise library:publish:release 17 | 18 | git status 19 | git push 20 | -------------------------------------------------------------------------------- /scripts/ci/steps/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | [ -n "$DEBUG" ] && set -x 4 | set -e 5 | set -o pipefail 6 | 7 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 8 | PROJECT_DIR="$( cd "$SCRIPT_DIR/../../.." && pwd )" 9 | 10 | cd "$PROJECT_DIR" 11 | 12 | ./go library:initialise library:test:all 13 | --------------------------------------------------------------------------------