├── .gitignore ├── tests ├── doc_examples │ ├── main.cpp │ ├── CMakeLists.txt │ ├── agent.cpp │ └── kv.cpp ├── main.cpp ├── consul-get │ ├── CMakeLists.txt │ └── consul_get.cpp ├── health │ ├── CMakeLists.txt │ └── health_tests.cpp ├── status │ ├── CMakeLists.txt │ └── status_tests.cpp ├── catalog │ └── CMakeLists.txt ├── sessions │ ├── CMakeLists.txt │ └── sessions_tests.cpp ├── coordinate │ ├── CMakeLists.txt │ └── coordinate_tests.cpp ├── unittests │ ├── CMakeLists.txt │ ├── consul.cpp │ ├── helpers.cpp │ └── parameters.cpp ├── agent │ ├── CMakeLists.txt │ └── agent_other_tests.cpp ├── kv │ └── CMakeLists.txt ├── consul │ ├── CMakeLists.txt │ └── consul_tests.cpp ├── consul.json.in ├── test-consul.bat ├── ca.cfg ├── test-consul.sh ├── CMakeLists.txt ├── test_consul.h ├── create-ssl.sh └── chrono_io.h ├── ext ├── catch │ └── README.md ├── b64 │ ├── README.md │ ├── cdecode.h │ ├── cencode.h │ ├── cencode.c │ └── cdecode.c └── json11 │ ├── README.md │ ├── CMakeLists.txt │ └── json11.hpp ├── conanfile.txt ├── ppconsul.pc.in ├── include └── ppconsul │ ├── ppconsul.h │ ├── config.h │ ├── http │ ├── status.h │ └── http_client.h │ ├── status.h │ ├── helpers.h │ ├── client_pool.h │ ├── error.h │ ├── response.h │ ├── parameters.h │ ├── types.h │ ├── coordinate.h │ ├── sessions.h │ ├── catalog.h │ └── health.h ├── src ├── status.cpp ├── health.cpp ├── client_pool.cpp ├── http_helpers.h ├── coordinate.cpp ├── catalog.cpp ├── sessions.cpp ├── s11n_types.h ├── CMakeLists.txt ├── helpers.cpp ├── s11n.h ├── curl │ ├── http_client.h │ └── http_client.cpp ├── consul.cpp ├── agent.cpp └── kv.cpp ├── LICENSE_1_0.txt ├── status.md ├── TODO.txt ├── CMakeLists.txt ├── rationales.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /workspace/ 2 | /ws/ 3 | /build/ 4 | -------------------------------------------------------------------------------- /tests/doc_examples/main.cpp: -------------------------------------------------------------------------------- 1 | int main() 2 | { 3 | return 0; 4 | } -------------------------------------------------------------------------------- /ext/catch/README.md: -------------------------------------------------------------------------------- 1 | This directory contains [Catch2](https://github.com/catchorg/Catch2) unit test framework. 2 | -------------------------------------------------------------------------------- /ext/b64/README.md: -------------------------------------------------------------------------------- 1 | This directory contains files from [libb64](http://libb64.sourceforge.net/) library for base64 encoding/decoding. 2 | -------------------------------------------------------------------------------- /conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | boost/[>1.55] 3 | libcurl/[>7.00] 4 | 5 | [generators] 6 | cmake_paths 7 | 8 | [options] 9 | libcurl:shared=True 10 | -------------------------------------------------------------------------------- /ext/json11/README.md: -------------------------------------------------------------------------------- 1 | This directory contains files from [modified version of json11](https://github.com/oliora/json11) library. 2 | It was derived from original [json11](https://github.com/dropbox/json11) library. 3 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #define CATCH_CONFIG_MAIN 8 | #include 9 | -------------------------------------------------------------------------------- /ppconsul.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=${prefix} 3 | includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ 4 | libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ 5 | 6 | Name: ppconsul 7 | Description: C++ client for the Consul HTTP API 8 | Version: @Ppconsul_VERSION@ 9 | Libs: -L${libdir} @ppconsul_libs@ 10 | Cflags: -I${includedir} 11 | -------------------------------------------------------------------------------- /tests/consul-get/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2020 Andrey Upadyshev 2 | # 3 | # Use, modification and distribution are subject to the 4 | # Boost Software License, Version 1.0. (See accompanying file 5 | # LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | project(consul-get) 8 | add_executable(${PROJECT_NAME} consul_get.cpp) 9 | link_test_libs(${PROJECT_NAME}) 10 | -------------------------------------------------------------------------------- /tests/health/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2020 Andrey Upadyshev 2 | # 3 | # Use, modification and distribution are subject to the 4 | # Boost Software License, Version 1.0. (See accompanying file 5 | # LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | project(health-tests) 8 | add_executable(${PROJECT_NAME} 9 | ../main.cpp 10 | health_tests.cpp 11 | ) 12 | link_test_libs(${PROJECT_NAME}) 13 | add_catch_test(${PROJECT_NAME}) 14 | -------------------------------------------------------------------------------- /tests/status/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2020 Andrey Upadyshev 2 | # 3 | # Use, modification and distribution are subject to the 4 | # Boost Software License, Version 1.0. (See accompanying file 5 | # LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | project(status-tests) 8 | add_executable(${PROJECT_NAME} 9 | ../main.cpp 10 | status_tests.cpp 11 | ) 12 | link_test_libs(${PROJECT_NAME}) 13 | add_catch_test(${PROJECT_NAME}) 14 | -------------------------------------------------------------------------------- /tests/catalog/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2020 Andrey Upadyshev 2 | # 3 | # Use, modification and distribution are subject to the 4 | # Boost Software License, Version 1.0. (See accompanying file 5 | # LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | project(catalog-tests) 8 | add_executable(${PROJECT_NAME} 9 | ../main.cpp 10 | catalog_tests.cpp 11 | ) 12 | link_test_libs(${PROJECT_NAME}) 13 | add_catch_test(${PROJECT_NAME}) 14 | -------------------------------------------------------------------------------- /tests/sessions/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019-2020 Andrey Upadyshev 2 | # 3 | # Use, modification and distribution are subject to the 4 | # Boost Software License, Version 1.0. (See accompanying file 5 | # LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | project(sessions-tests) 8 | add_executable(${PROJECT_NAME} 9 | ../main.cpp 10 | sessions_tests.cpp 11 | ) 12 | link_test_libs(${PROJECT_NAME}) 13 | add_catch_test(${PROJECT_NAME}) 14 | -------------------------------------------------------------------------------- /tests/coordinate/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2020 Andrey Upadyshev 2 | # 3 | # Use, modification and distribution are subject to the 4 | # Boost Software License, Version 1.0. (See accompanying file 5 | # LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | project(coordinate-tests) 8 | add_executable(${PROJECT_NAME} 9 | ../main.cpp 10 | coordinate_tests.cpp 11 | ) 12 | link_test_libs(${PROJECT_NAME}) 13 | add_catch_test(${PROJECT_NAME}) 14 | -------------------------------------------------------------------------------- /tests/unittests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2020 Andrey Upadyshev 2 | # 3 | # Use, modification and distribution are subject to the 4 | # Boost Software License, Version 1.0. (See accompanying file 5 | # LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | project(unittests) 8 | add_executable(${PROJECT_NAME} ../main.cpp 9 | consul.cpp 10 | helpers.cpp 11 | parameters.cpp 12 | ) 13 | link_test_libs(${PROJECT_NAME}) 14 | add_catch_test(${PROJECT_NAME}) 15 | -------------------------------------------------------------------------------- /tests/agent/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2020 Andrey Upadyshev 2 | # 3 | # Use, modification and distribution are subject to the 4 | # Boost Software License, Version 1.0. (See accompanying file 5 | # LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | project(agent-tests) 8 | add_executable(${PROJECT_NAME} 9 | ../main.cpp 10 | agent_checks_tests.cpp 11 | agent_services_tests.cpp 12 | agent_other_tests.cpp 13 | ) 14 | link_test_libs(${PROJECT_NAME}) 15 | add_catch_test(${PROJECT_NAME}) 16 | -------------------------------------------------------------------------------- /tests/kv/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2020 Andrey Upadyshev 2 | # 3 | # Use, modification and distribution are subject to the 4 | # Boost Software License, Version 1.0. (See accompanying file 5 | # LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | project(kv-tests) 8 | add_executable(${PROJECT_NAME} 9 | ../main.cpp 10 | kv_tests.cpp 11 | ) 12 | link_test_libs(${PROJECT_NAME}) 13 | find_package(Threads) 14 | target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT}) 15 | add_catch_test(${PROJECT_NAME}) 16 | -------------------------------------------------------------------------------- /tests/consul/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2020 Andrey Upadyshev 2 | # 3 | # Use, modification and distribution are subject to the 4 | # Boost Software License, Version 1.0. (See accompanying file 5 | # LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | project(consul-tests) 8 | add_executable(${PROJECT_NAME} 9 | ../main.cpp 10 | consul_tests.cpp 11 | ) 12 | link_test_libs(${PROJECT_NAME}) 13 | find_package(Threads) 14 | target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT}) 15 | add_catch_test(${PROJECT_NAME}) 16 | -------------------------------------------------------------------------------- /include/ppconsul/ppconsul.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #pragma once 8 | 9 | // Meta header which includes all Ppconsul headers needed to the library user 10 | 11 | #include "ppconsul/config.h" 12 | 13 | #include "ppconsul/consul.h" 14 | #include "ppconsul/kv.h" 15 | #include "ppconsul/agent.h" 16 | #include "ppconsul/catalog.h" 17 | #include "ppconsul/status.h" 18 | -------------------------------------------------------------------------------- /tests/doc_examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2020 Andrey Upadyshev 2 | # 3 | # Use, modification and distribution are subject to the 4 | # Boost Software License, Version 1.0. (See accompanying file 5 | # LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | cmake_minimum_required(VERSION 3.1) 8 | 9 | # Should be uncommented for external project 10 | #find_package(ppconsul REQUIRED) 11 | 12 | project(doc_examples) 13 | add_executable(${PROJECT_NAME} 14 | agent.cpp 15 | kv.cpp 16 | main.cpp 17 | ) 18 | 19 | target_link_libraries(${PROJECT_NAME} ppconsul) 20 | -------------------------------------------------------------------------------- /tests/consul.json.in: -------------------------------------------------------------------------------- 1 | { 2 | "datacenter": "ppconsul_test", 3 | "enable_script_checks": true, 4 | "log_level": "trace", 5 | "ca_file": "${CMAKE_SOURCE_DIR}/tests/tls/ca/ca.cert", 6 | "cert_file": "${CMAKE_SOURCE_DIR}/tests/tls/certs/consul.cert", 7 | "key_file": "${CMAKE_SOURCE_DIR}/tests/tls/certs/consul.key", 8 | "encrypt": "B2dD7ImCezplButFL27cFw==", 9 | "verify_incoming": true, 10 | "verify_outgoing": true, 11 | "addresses": { 12 | "http": "0.0.0.0", 13 | "https": "0.0.0.0" 14 | }, 15 | "ports": { 16 | "http": 8500, 17 | "https": 8080 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /include/ppconsul/config.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #pragma once 8 | 9 | #if defined _MSC_VER && _MSC_VER < 1900 // MS Visual Studio before VS2014 10 | #define PPCONSUL_NO_CXX11_NOEXCEPT 11 | 12 | #if ! defined PPCONSUL_SNPRINTF_DEFINED && ! defined snprintf 13 | #define PPCONSUL_SNPRINTF_DEFINED 14 | #define snprintf _snprintf 15 | #endif 16 | #endif 17 | 18 | #if defined PPCONSUL_NO_CXX11_NOEXCEPT 19 | #define noexcept 20 | #endif 21 | -------------------------------------------------------------------------------- /src/status.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | 8 | #include "ppconsul/status.h" 9 | #include "s11n_types.h" 10 | 11 | namespace ppconsul { namespace status { 12 | 13 | namespace impl { 14 | 15 | std::string parseLeader(const std::string& json) 16 | { 17 | return s11n::parseJson(json); 18 | } 19 | 20 | StringList parsePeers(const std::string& json) 21 | { 22 | return s11n::parseJson(json); 23 | } 24 | 25 | } 26 | }} 27 | -------------------------------------------------------------------------------- /ext/b64/cdecode.h: -------------------------------------------------------------------------------- 1 | /* 2 | cdecode.h - c header for a base64 decoding algorithm 3 | 4 | This is part of the libb64 project, and has been placed in the public domain. 5 | For details, see http://sourceforge.net/projects/libb64 6 | */ 7 | 8 | #ifndef BASE64_CDECODE_H 9 | #define BASE64_CDECODE_H 10 | 11 | typedef enum 12 | { 13 | step_a, step_b, step_c, step_d 14 | } base64_decodestep; 15 | 16 | typedef struct 17 | { 18 | base64_decodestep step; 19 | char plainchar; 20 | } base64_decodestate; 21 | 22 | void base64_init_decodestate(base64_decodestate* state_in); 23 | 24 | int base64_decode_value(char value_in); 25 | 26 | int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in); 27 | 28 | #endif /* BASE64_CDECODE_H */ 29 | 30 | -------------------------------------------------------------------------------- /ext/b64/cencode.h: -------------------------------------------------------------------------------- 1 | /* 2 | cencode.h - c header for a base64 encoding algorithm 3 | 4 | This is part of the libb64 project, and has been placed in the public domain. 5 | For details, see http://sourceforge.net/projects/libb64 6 | */ 7 | 8 | #ifndef BASE64_CENCODE_H 9 | #define BASE64_CENCODE_H 10 | 11 | typedef enum 12 | { 13 | step_A, step_B, step_C 14 | } base64_encodestep; 15 | 16 | typedef struct 17 | { 18 | base64_encodestep step; 19 | char result; 20 | } base64_encodestate; 21 | 22 | void base64_init_encodestate(base64_encodestate* state_in); 23 | 24 | char base64_encode_value(char value_in); 25 | 26 | int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); 27 | 28 | int base64_encode_blockend(char* code_out, base64_encodestate* state_in); 29 | 30 | #endif /* BASE64_CENCODE_H */ 31 | 32 | -------------------------------------------------------------------------------- /tests/sessions/sessions_tests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | #include "ppconsul/sessions.h" 9 | #include "test_consul.h" 10 | 11 | using namespace ppconsul::sessions; 12 | 13 | TEST_CASE("sessions.basic-management", "[consul][sessions]") 14 | { 15 | auto consul = create_test_consul(); 16 | Sessions manager(consul); 17 | 18 | auto session = manager.create(); 19 | 20 | manager.renew(session); 21 | 22 | REQUIRE(manager.destroy(session)); 23 | 24 | // the destroy operation is idempotent, should return true for non-existing session 25 | REQUIRE(manager.destroy(session)); 26 | } 27 | -------------------------------------------------------------------------------- /tests/test-consul.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set DATA_DIR=test-consul-data 4 | 5 | if "%1"=="start" ( 6 | consul --version 7 | 8 | consul info > nul 9 | if NOT ERRORLEVEL 1 (echo Consul is already running && exit /b 1) 10 | if EXIST "%DATA_DIR%" (rd /S /Q "%DATA_DIR%" || echo Can not delete data dir && exit /b 1) 11 | start consul agent -server -bootstrap-expect=1 -dc=ppconsul_test "-data-dir=%DATA_DIR%" -advertise=127.0.0.1 12 | rem Use ping to sleep for 3 seconds 13 | ping 127.0.0.1 -n 3 > nul 14 | consul info > nul 15 | if ERRORLEVEL 1 (echo Can not start Consul && exit /b 1) 16 | echo Consul started successfully 17 | exit /b 0 18 | ) 19 | 20 | if "%1"=="stop" ( 21 | consul info > nul 22 | if ERRORLEVEL 1 (echo Consul is not running && exit /b 0) 23 | consul leave 24 | if ERRORLEVEL 1 exit /b 1 25 | exit /b 0 26 | ) 27 | 28 | echo Usage: test-consul start^|stop 29 | exit /b 2 30 | -------------------------------------------------------------------------------- /tests/ca.cfg: -------------------------------------------------------------------------------- 1 | [ ca ] 2 | default_ca = ppconsul 3 | 4 | [ ppconsul ] 5 | dir = . 6 | unique_subject = no 7 | default_days = 36500 8 | default_crl_days = 36500 9 | default_md = sha256 10 | policy = ppconsul_policy 11 | x509_extensions = ppconsul_extensions 12 | certificate = $dir/ca.cert 13 | database = $dir/certindex 14 | private_key = $dir/privkey.pem 15 | serial = $dir/serial 16 | new_certs_dir = $dir/newcerts 17 | 18 | [ ppconsul_policy ] 19 | commonName = supplied 20 | stateOrProvinceName = optional 21 | countryName = optional 22 | emailAddress = optional 23 | organizationName = optional 24 | organizationalUnitName = optional 25 | 26 | [ ppconsul_extensions ] 27 | basicConstraints = CA:false 28 | subjectKeyIdentifier = hash 29 | authorityKeyIdentifier = keyid:always 30 | keyUsage = digitalSignature,keyEncipherment 31 | extendedKeyUsage = serverAuth,clientAuth 32 | 33 | [req] 34 | distinguished_name = req_distinguished_name 35 | 36 | [req_distinguished_name] 37 | -------------------------------------------------------------------------------- /tests/test-consul.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Run test consul. usage: test-consul.sh start|stop 3 | 4 | case "$1" in 5 | start) 6 | CONSUL_VERSION=`consul --version` 7 | echo "$CONSUL_VERSION" 8 | 9 | consul info >/dev/null 10 | if [ $? -eq 0 ]; then 11 | echo "Consul is already running" 12 | exit 1 13 | fi 14 | 15 | DATA_DIR=test-consul-data 16 | rm -rf "$DATA_DIR" || (echo "Can not delete data dir" && exit 1) 17 | consul agent -bootstrap-expect=1 -server -dc=ppconsul_test "-data-dir=$DATA_DIR" -advertise=127.0.0.1 >/dev/null & 18 | sleep 3s 19 | consul info >/dev/null 20 | if [ $? -ne 0 ]; then 21 | echo "Failed to start consul" 22 | exit 1 23 | else 24 | echo "Consul started successfully" 25 | fi 26 | ;; 27 | stop) 28 | consul info >/dev/null 29 | if [ $? -ne 0 ]; then 30 | echo "Consul is not running" 31 | exit 0 32 | fi 33 | consul leave || exit 1 34 | ;; 35 | *) 36 | echo "Usage: test-consul.sh start|stop" 37 | exit 2 38 | ;; 39 | esac 40 | -------------------------------------------------------------------------------- /include/ppconsul/http/status.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | 13 | namespace ppconsul { namespace http { 14 | 15 | class Status 16 | { 17 | public: 18 | explicit Status(int code = 0) noexcept 19 | : m_code(code) 20 | {} 21 | 22 | Status(int code, std::string message) 23 | : m_code(code) 24 | , m_message(std::move(message)) 25 | {} 26 | 27 | // Returns true if code() is 2xx (i.e. success) and false otherwise 28 | bool success() const noexcept{ return 2 == m_code / 100; } 29 | 30 | int code() const noexcept{ return m_code; } 31 | const std::string& message() const noexcept{ return m_message; } 32 | 33 | private: 34 | int m_code; 35 | std::string m_message; 36 | }; 37 | 38 | }} 39 | -------------------------------------------------------------------------------- /src/health.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include "ppconsul/health.h" 8 | #include "s11n_types.h" 9 | 10 | 11 | namespace json11 { 12 | inline void load(const json11::Json& src, ppconsul::health::NodeServiceChecks& dst) 13 | { 14 | using ppconsul::s11n::load; 15 | 16 | load(src, std::get<0>(dst), "Node"); 17 | load(src, std::get<1>(dst), "Service"); 18 | load(src, std::get<2>(dst), "Checks"); 19 | } 20 | } 21 | 22 | namespace ppconsul { namespace health { 23 | namespace impl { 24 | std::vector parseCheckInfos(const std::string& json) 25 | { 26 | return s11n::parseJson>(json); 27 | } 28 | 29 | std::vector parseService(const std::string& json) 30 | { 31 | return s11n::parseJson>(json); 32 | } 33 | } 34 | }} 35 | 36 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2020 Andrey Upadyshev 2 | # 3 | # Use, modification and distribution are subject to the 4 | # Boost Software License, Version 1.0. (See accompanying file 5 | # LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | macro(link_test_libs project_name) 8 | target_include_directories(${project_name} PRIVATE 9 | ${Boost_INCLUDE_DIRS} 10 | ${CMAKE_SOURCE_DIR}/ext 11 | ${CMAKE_SOURCE_DIR}/tests 12 | ) 13 | 14 | target_link_libraries(${project_name} ppconsul) 15 | endmacro() 16 | 17 | macro(add_catch_test name) 18 | if (TEST_REPORT_FORMAT) 19 | add_test(NAME ${name} COMMAND ${name} -r ${TEST_REPORT_FORMAT} -o "${name}.test_out.xml") 20 | else() 21 | add_test(NAME ${name} COMMAND ${name}) 22 | endif() 23 | endmacro() 24 | 25 | configure_file(consul.json.in consul.json) 26 | 27 | add_subdirectory(unittests) # to run unittests before consul ones 28 | 29 | add_subdirectory(consul) 30 | add_subdirectory(agent) 31 | add_subdirectory(catalog) 32 | add_subdirectory(coordinate) 33 | add_subdirectory(kv) 34 | add_subdirectory(sessions) 35 | add_subdirectory(status) 36 | 37 | add_subdirectory(consul-get) 38 | add_subdirectory(doc_examples) 39 | -------------------------------------------------------------------------------- /src/client_pool.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | 9 | 10 | namespace ppconsul { 11 | 12 | std::unique_ptr ClientPool::acquire() 13 | { 14 | { 15 | std::lock_guard lock(m_mutex); 16 | 17 | if (!m_pool.empty()) 18 | { 19 | auto res = std::move(m_pool.back()); 20 | m_pool.pop_back(); 21 | return res; 22 | } 23 | } 24 | 25 | return m_factory(); 26 | } 27 | 28 | void ClientPool::release(http::HttpClient *client) noexcept 29 | { 30 | assert(client); 31 | std::unique_ptr cl(client); 32 | 33 | // We just delete client if there is no free memory for adding it back to the pool 34 | try 35 | { 36 | std::lock_guard lock(m_mutex); 37 | m_pool.push_back(std::move(cl)); 38 | } 39 | catch (const std::bad_alloc&) 40 | {} 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /ext/json11/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2020 Andrey Upadyshev 2 | # 3 | # Use, modification and distribution are subject to the 4 | # Boost Software License, Version 1.0. (See accompanying file 5 | # LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | project(json11) 8 | 9 | add_library(${PROJECT_NAME} STATIC json11.hpp json11.cpp) 10 | 11 | target_compile_features(${PROJECT_NAME} PUBLIC cxx_auto_type cxx_decltype cxx_static_assert cxx_rvalue_references) 12 | 13 | set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER ${PROJECT_NAME} 14 | COMPILE_PDB_NAME ${PROJECT_NAME} 15 | COMPILE_PDB_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} 16 | FOLDER ${PROJECT_NAME} 17 | ) 18 | 19 | 20 | if (BUILD_STATIC_LIB) 21 | install( 22 | TARGETS ${PROJECT_NAME} 23 | EXPORT ${CMAKE_PROJECT_NAME} 24 | LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" 25 | ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" 26 | RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" 27 | ) 28 | if(WIN32) 29 | install( 30 | FILES ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${PROJECT_NAME}.pdb 31 | DESTINATION "${CMAKE_INSTALL_LIBDIR}" 32 | OPTIONAL 33 | ) 34 | endif() 35 | endif() 36 | 37 | -------------------------------------------------------------------------------- /tests/status/status_tests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | 9 | #include "ppconsul/status.h" 10 | #include "test_consul.h" 11 | #include 12 | #include 13 | #include 14 | 15 | 16 | using namespace ppconsul::status; 17 | using ppconsul::CheckStatus; 18 | 19 | TEST_CASE("status.leader", "[consul][status][leader]") 20 | { 21 | auto consul = create_test_consul(); 22 | Status status(consul); 23 | 24 | auto leader = status.leader(); 25 | CHECK(leader == get_test_leader()); 26 | 27 | auto elected = status.isLeaderElected(); 28 | CHECK(elected); 29 | 30 | // TBD: Can this method be tested with consul having no leader elected? 31 | // Expect leader() to return empty string in that case 32 | } 33 | 34 | TEST_CASE("status.peers", "[consul][status][peers]") 35 | { 36 | auto consul = create_test_consul(); 37 | Status status(consul); 38 | 39 | auto peers = status.peers(); 40 | CHECK(peers == ppconsul::StringList({get_test_leader()})); 41 | } 42 | -------------------------------------------------------------------------------- /src/http_helpers.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | 13 | namespace ppconsul { namespace http { namespace impl { 14 | const std::string Index_Header_Name("X-Consul-Index"); 15 | const std::string KnownLeader_Header_Name("X-Consul-Knownleader"); 16 | const std::string LastContact_Headers_Name("X-Consul-Lastcontact"); 17 | 18 | inline uint64_t uint64_headerValue(const char *v) 19 | { 20 | return std::strtoull(v, nullptr, 10); 21 | } 22 | 23 | inline bool bool_headerValue(const char *v) 24 | { 25 | return 0 == strcmp(v, "true"); 26 | } 27 | 28 | inline std::string makeUrl(const std::string& addr, const std::string& path, const std::string& query) 29 | { 30 | std::string res; 31 | res.reserve(addr.size() + path.size() + query.size() + 1); // +1 for '?' 32 | res += addr; 33 | res += path; 34 | if (!query.empty()) 35 | { 36 | res += '?'; 37 | res += query; 38 | } 39 | return res; 40 | } 41 | }}} 42 | -------------------------------------------------------------------------------- /tests/test_consul.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ppconsul/consul.h" 4 | #include "chrono_io.h" 5 | #include 6 | 7 | 8 | inline std::string get_test_datacenter() 9 | { 10 | auto datacenter = std::getenv("PPCONSUL_TEST_DC"); 11 | return datacenter ? datacenter : "ppconsul_test"; 12 | } 13 | 14 | template 15 | inline ppconsul::Consul create_test_consul(AdditionalParams&&... additionalParams) 16 | { 17 | auto addr = std::getenv("PPCONSUL_TEST_ADDR"); 18 | 19 | return ppconsul::Consul( 20 | addr ? addr : ppconsul::Default_Server_Endpoint, 21 | ppconsul::kw::dc = get_test_datacenter(), 22 | std::forward(additionalParams)... 23 | ); 24 | } 25 | 26 | inline std::string get_test_leader() 27 | { 28 | auto leader = std::getenv("PPCONSUL_TEST_LEADER_ADDR"); 29 | return leader ? leader : "127.0.0.1:8300"; 30 | } 31 | 32 | 33 | #define REQUIRE_NOTHROW_OR_STATUS(expr, statusCode) \ 34 | REQUIRE_NOTHROW([&](){ \ 35 | try { \ 36 | expr; \ 37 | } catch (const ppconsul::BadStatus& ex) { \ 38 | if (ex.code() != statusCode) \ 39 | throw; \ 40 | } \ 41 | }()) 42 | -------------------------------------------------------------------------------- /LICENSE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /tests/doc_examples/agent.cpp: -------------------------------------------------------------------------------- 1 | #include "ppconsul/agent.h" 2 | 3 | 4 | void agent_service() 5 | { 6 | // ### Register, deregister and report the state of your service in Consul: 7 | 8 | using ppconsul::Consul; 9 | using namespace ppconsul::agent; 10 | 11 | // Create a consul client that uses default local endpoint `http://127.0.0.1:8500` and default data center 12 | Consul consul; 13 | // We need the 'agent' endpoint for a service registration 14 | Agent agent(consul); 15 | 16 | // Register a service with associated HTTP check: 17 | agent.registerService( 18 | kw::name = "my-service", 19 | kw::port = 9876, 20 | kw::tags = {"tcp", "super_server"}, 21 | kw::check = HttpCheck{"http://localhost:80/", std::chrono::seconds(2)} 22 | ); 23 | 24 | // Unregister service 25 | agent.deregisterService("my-service"); 26 | 27 | // ... 28 | 29 | // Register a service with TTL 30 | agent.registerService( 31 | kw::name = "my-service", 32 | kw::port = 9876, 33 | kw::id = "my-service-1", 34 | kw::check = TtlCheck{std::chrono::seconds(5)} 35 | ); 36 | 37 | // Report service is OK 38 | agent.servicePass("my-service-1"); 39 | 40 | // Report service is failed 41 | agent.serviceFail("my-service-1", "Disk is full"); 42 | } 43 | -------------------------------------------------------------------------------- /tests/create-ssl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Based on 3 | # https://www.digitalocean.com/community/tutorials/how-to-secure-consul-with-tls-encryption-on-ubuntu-14-04 4 | 5 | set -ex 6 | 7 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 8 | NEWCERTTOP="$DIR/tls/certs" 9 | CATOP="$DIR/tls/ca" 10 | 11 | function create_ca { 12 | [ ! -d $CATOP ] || (echo "CA directory already exists" && exit 1) 13 | mkdir -p $CATOP/newcerts 14 | cp $DIR/ca.cfg $CATOP/ca.cfg 15 | touch $CATOP/certindex 16 | echo "000a" > $CATOP/serial 17 | openssl req -x509 -newkey rsa:2048 -nodes -keyout $CATOP/privkey.pem -out $CATOP/ca.cert -subj "/C=NL/L=The Hague/O=ppconsul-test/CN=ca" 18 | } 19 | 20 | # Create CA authority if doesn't exist 21 | [ -d $CATOP ] || create_ca 22 | 23 | # Generate new certificate 24 | mkdir -p $NEWCERTTOP 25 | 26 | openssl req -newkey rsa:1024 -nodes -out $NEWCERTTOP/consul.csr -keyout $NEWCERTTOP/consul.key -config $CATOP/ca.cfg -subj "/C=NL/L=The Hague/O=ppconsul-test/CN=localhost" 27 | 28 | # Can't make relative dirs in config work 29 | (cd $CATOP && openssl ca -batch -config $CATOP/ca.cfg -notext -in $NEWCERTTOP/consul.csr -cert $CATOP/ca.cert -keyfile $CATOP/privkey.pem -out $NEWCERTTOP/consul.cert) 30 | 31 | rm $NEWCERTTOP/consul.csr 32 | 33 | # On macOS we have to use PKCS12 format with password 34 | openssl pkcs12 -export -in $NEWCERTTOP/consul.cert -inkey $NEWCERTTOP/consul.key -out $NEWCERTTOP/consul.p12 -passout pass:thepassword 35 | -------------------------------------------------------------------------------- /include/ppconsul/status.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #pragma once 8 | 9 | #include "ppconsul/consul.h" 10 | #include "ppconsul/helpers.h" 11 | #include "ppconsul/types.h" 12 | #include 13 | 14 | namespace ppconsul { namespace status { 15 | 16 | namespace impl { 17 | std::string parseLeader(const std::string&); 18 | 19 | std::vector parsePeers(const std::string&); 20 | } 21 | 22 | class Status 23 | { 24 | public: 25 | explicit Status(Consul& consul) 26 | : m_consul(consul) 27 | {} 28 | 29 | boost::optional leader() const 30 | { 31 | auto leader = impl::parseLeader(m_consul.get("/v1/status/leader")); 32 | if (!leader.empty()) 33 | return std::move(leader); 34 | return {}; 35 | } 36 | 37 | bool isLeaderElected() const 38 | { 39 | return leader() ? true : false; 40 | } 41 | 42 | std::vector peers() const 43 | { 44 | return impl::parsePeers(m_consul.get("/v1/status/peers")); 45 | } 46 | 47 | private: 48 | Consul& m_consul; 49 | }; 50 | 51 | }} 52 | -------------------------------------------------------------------------------- /src/coordinate.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | 8 | #include "ppconsul/coordinate.h" 9 | #include "s11n_types.h" 10 | 11 | namespace ppconsul { namespace coordinate { 12 | void load(const json11::Json& src, ppconsul::coordinate::Node& dst) 13 | { 14 | using ppconsul::s11n::load; 15 | 16 | load(src, dst.node, "Node"); 17 | load(src, dst.segment, "Segment"); 18 | load(src, dst.coord, "Coord"); 19 | } 20 | 21 | void load(const json11::Json& src, ppconsul::coordinate::Datacenter& dst) 22 | { 23 | using ppconsul::s11n::load; 24 | 25 | load(src, dst.datacenter, "Datacenter"); 26 | load(src, dst.areaId, "AreaID"); 27 | load(src, dst.coordinates, "Coordinates"); 28 | } 29 | 30 | namespace impl { 31 | 32 | std::vector parseDatacenters(const std::string& json) 33 | { 34 | return s11n::parseJson>(json); 35 | } 36 | 37 | std::vector parseNodes(const std::string& json) 38 | { 39 | return s11n::parseJson>(json); 40 | } 41 | 42 | Node parseNode(const std::string& json) 43 | { 44 | return s11n::parseJson(json); 45 | } 46 | 47 | } 48 | 49 | }} 50 | -------------------------------------------------------------------------------- /tests/health/health_tests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | 9 | #include "ppconsul/health.h" 10 | #include "ppconsul/agent.h" 11 | #include "test_consul.h" 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | using ppconsul::health::Health; 18 | using ppconsul::agent::Agent; 19 | using ppconsul::CheckStatus; 20 | using ppconsul::Consistency; 21 | 22 | 23 | namespace { 24 | std::map make_checks_map(std::vector checks) 25 | { 26 | std::map res; 27 | for(auto& i: checks) 28 | res.emplace(i.name, std::move(i)); 29 | return res; 30 | } 31 | } 32 | 33 | TEST_CASE("agent.health", "[consul][health]") 34 | { 35 | auto consul = create_test_consul(); 36 | Health health(consul); 37 | Agent agent(consul); 38 | 39 | agent.deregisterCheck("check1"); 40 | agent.deregisterService("service1"); 41 | 42 | const auto selfMember = Agent(consul).self().second; 43 | 44 | agent.registerCheck({ "check1" }, std::chrono::seconds(12)); 45 | agent.registerService({ "service1" }, std::chrono::minutes(5)); 46 | 47 | auto nodeChecks = health.node(selfMember.name); 48 | CHECK(nodeChecks.size() >= 3); 49 | 50 | std::map 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include "ppconsul/consul.h" 8 | #include 9 | 10 | 11 | TEST_CASE( "consul.BadStatus", "[http][consul][status][error]" ) 12 | { 13 | using namespace ppconsul; 14 | 15 | CHECK(BadStatus(http::Status(500, "Internal Server Error"), "No path to datacenter").what() == std::string("No path to datacenter [500 Internal Server Error]")); 16 | CHECK(BadStatus(http::Status(404, "Not Found")).what() == std::string("404 Not Found")); 17 | CHECK(BadStatus(http::Status(0, "Nothing")).what() == std::string("000 Nothing")); 18 | CHECK(BadStatus(http::Status(9999, "Wrong Long Code")).what() == std::string("9999 Wrong Long Code")); 19 | CHECK(std::string(BadStatus(http::Status(1)).what()).find("001") == 0); 20 | } 21 | 22 | TEST_CASE( "consul.throwStatusError", "[http][consul][status][error]" ) 23 | { 24 | using namespace ppconsul; 25 | 26 | CHECK_THROWS_AS(throwStatusError(http::Status(500, "Internal Server Error"), "No path to datacenter"), BadStatus); 27 | CHECK_THROWS_AS(throwStatusError(http::Status(500, "Internal Server Error")), BadStatus); 28 | CHECK_THROWS_AS(throwStatusError(http::Status(500)), BadStatus); 29 | CHECK_THROWS_AS(throwStatusError(http::Status(404, "Not Found"), "Something"), NotFoundError); 30 | CHECK_THROWS_AS(throwStatusError(http::Status(404, "Not Found")), NotFoundError); 31 | CHECK_THROWS_AS(throwStatusError(http::Status(404)), NotFoundError); 32 | } 33 | -------------------------------------------------------------------------------- /tests/consul-get/consul_get.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | #include "ppconsul/ppconsul.h" 9 | 10 | 11 | using namespace ppconsul; 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | if (argc < 2) 16 | { 17 | std::cout << "Usage: " << argv[0] << " path-to-fetch" << std::endl; 18 | return 2; 19 | } 20 | 21 | try 22 | { 23 | Consul consul("https://localhost:8080", 24 | kw::tls::cert="/home/oliora/projects/ppconsul/tests/tls/certs/consul.cert", 25 | kw::tls::key="/home/oliora/projects/ppconsul/tests/tls/certs/consul.key", 26 | kw::tls::ca_info="/home/oliora/projects/ppconsul/tests/tls/ca/ca.cert" 27 | ); 28 | 29 | http::Status s; 30 | auto r = consul.get(s, argv[1]); 31 | std::cout 32 | << s.code() << ' ' << s.message() << '\n'; 33 | if (r.headers()) 34 | { 35 | std::cout 36 | << "Index: " << r.headers().index() << '\n' 37 | << "Known leader: " << (r.headers().knownLeader() ? "true" : "false") << '\n' 38 | << "Lastcontact: " << r.headers().lastContact().count() << "\n\n"; 39 | } 40 | else 41 | { 42 | std::cout << '\n'; 43 | } 44 | 45 | std::cout << r.data() << std::endl; 46 | } 47 | catch (const std::exception& ex) 48 | { 49 | std::cerr << "Error: " << ex.what() << std::endl; 50 | return 1; 51 | } 52 | 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /tests/doc_examples/kv.cpp: -------------------------------------------------------------------------------- 1 | #include "ppconsul/kv.h" 2 | 3 | 4 | void kv() 5 | { 6 | 7 | // ### Use Key-Value storage: 8 | 9 | using ppconsul::Consul; 10 | using ppconsul::Consistency; 11 | using namespace ppconsul::kv; 12 | 13 | // Create a consul client that uses default local endpoint `http://127.0.0.1:8500` and default data center 14 | Consul consul; 15 | 16 | // We need the 'kv' endpoint 17 | Kv kv(consul); 18 | 19 | // Read the value of a key from the storage 20 | std::string something = kv.get("settings.something", "default-value"); 21 | 22 | // Read the value of a key from the storage with consistency mode specified 23 | something = kv.get("settings.something", "default-value", kw::consistency = Consistency::Consistent); 24 | 25 | // Erase a key from the storage 26 | kv.erase("settings.something-else"); 27 | 28 | // Set the value of a key 29 | kv.set("settings.something", "new-value"); 30 | } 31 | 32 | void kv_blocking_query() 33 | { 34 | // ### Blocking query to Key-Value storage: 35 | 36 | using ppconsul::Consul; 37 | using namespace ppconsul::kv; 38 | 39 | // Create a consul client that uses default local endpoint `http://127.0.0.1:8500` and default data center 40 | Consul consul; 41 | 42 | // We need the 'kv' endpoint 43 | Kv kv(consul); 44 | 45 | // Get key+value+metadata item 46 | KeyValue item = kv.item("status.last-event-id"); 47 | 48 | // Wait for the item change for no more than 1 minute: 49 | item = kv.item("status.last-event-id", kw::block_for = {std::chrono::minutes(1), item.modifyIndex}); 50 | 51 | // If key exists, print it: 52 | if (item) 53 | std::cout << item.key << "=" << item.value << "\n"; 54 | } 55 | -------------------------------------------------------------------------------- /include/ppconsul/helpers.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #pragma once 8 | 9 | #include "ppconsul/config.h" 10 | #include "ppconsul/error.h" 11 | #include 12 | #include 13 | 14 | 15 | namespace ppconsul { namespace helpers { 16 | 17 | template 18 | std::string format(const char *fmt, const T&...t) 19 | { 20 | const auto len = snprintf(nullptr, 0, fmt, t...); 21 | std::string r; 22 | r.resize(static_cast(len) + 1); 23 | snprintf(&r.front(), len + 1, fmt, t...); // Bad boy 24 | r.resize(static_cast(len)); 25 | 26 | return r; 27 | } 28 | 29 | inline bool urlHasScheme(const std::string& url) 30 | { 31 | return url.find("://") != std::string::npos; 32 | } 33 | 34 | // Ensures that URL has scheme. Adds `defaultScheme` if no scheme is present yet. 35 | inline std::string ensureScheme(const std::string& url, const std::string& defaultScheme = "http") 36 | { 37 | if (urlHasScheme(url)) 38 | return url; 39 | 40 | return defaultScheme + "://" + url; 41 | } 42 | 43 | // Note that implementation used is liberal to the input: it allows characters outside the alphabete and 44 | // incorrect padding. 45 | std::string decodeBase64(const std::string& s); 46 | 47 | std::string encodeBase64(const std::string &s); 48 | 49 | // Encode string to be safely used as *part* of URL. 50 | std::string encodeUrl(const std::string&s); 51 | 52 | bool parseJsonBool(const std::string& s); 53 | }} 54 | -------------------------------------------------------------------------------- /include/ppconsul/client_pool.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace ppconsul { 18 | 19 | class ClientPool 20 | { 21 | public: 22 | struct ReleaseToClientPool 23 | { 24 | ReleaseToClientPool(ClientPool& pool) noexcept : m_pool(&pool) {} 25 | 26 | void operator() (http::HttpClient *client) const noexcept { m_pool->release(client); } 27 | 28 | private: 29 | ClientPool *m_pool; 30 | }; 31 | 32 | using ClientPtr = std::unique_ptr; 33 | using Factory = std::function()>; 34 | 35 | explicit ClientPool(Factory factory) 36 | : m_factory(std::move(factory)) {} 37 | 38 | ClientPool(ClientPool&&) = delete; 39 | ClientPool(const ClientPool&) = delete; 40 | ClientPool& operator=(const ClientPool&) = delete; 41 | ClientPool& operator=(ClientPool&&) = delete; 42 | 43 | ClientPtr operator() () { return get(); } 44 | ClientPtr get() { return ClientPtr{acquire().release(), ReleaseToClientPool{*this}}; } 45 | 46 | private: 47 | std::unique_ptr acquire(); 48 | void release(http::HttpClient *client) noexcept; 49 | 50 | const Factory m_factory; 51 | 52 | std::mutex m_mutex; 53 | std::vector> m_pool; 54 | }; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /tests/consul/consul_tests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | 9 | #include "ppconsul/consul.h" 10 | #include "ppconsul/kv.h" 11 | #include "test_consul.h" 12 | #include 13 | #include 14 | 15 | 16 | using namespace ppconsul; 17 | 18 | 19 | TEST_CASE("consul.request_timeout", "[consul][http]") 20 | { 21 | auto consul = create_test_consul(kw::request_timeout = std::chrono::milliseconds{10}); 22 | kv::Kv kv(consul); 23 | 24 | kv.set("key1", "value1"); 25 | auto index1 = kv.item(ppconsul::withHeaders, "key1").headers().index(); 26 | 27 | auto t1 = std::chrono::steady_clock::now(); 28 | REQUIRE_THROWS_AS(kv.item(ppconsul::withHeaders, "key1", kw::block_for = {std::chrono::milliseconds(500), index1}), RequestTimedOut); 29 | auto time = std::chrono::steady_clock::now() - t1; 30 | CHECK(time >= std::chrono::milliseconds{10}); 31 | CHECK(time < std::chrono::milliseconds{100}); // Timeout mechanism is not guaranteed to be very precise 32 | } 33 | 34 | TEST_CASE("consul.connect_timeout", "[consul][http]") 35 | { 36 | // This test may fail if libCURL is not compiled with asynchronous DNS resolve library like c-ares 37 | 38 | Consul consul{"http://10.255.255.1:81", kw::connect_timeout = std::chrono::milliseconds{2}}; 39 | 40 | auto t1 = std::chrono::steady_clock::now(); 41 | REQUIRE_THROWS_AS(consul.get("/"), RequestTimedOut); 42 | auto time = std::chrono::steady_clock::now() - t1; 43 | CHECK(time >= std::chrono::milliseconds{2}); 44 | CHECK(time < std::chrono::milliseconds{100}); // Timeout mechanism is not guaranteed to be very precise 45 | } 46 | -------------------------------------------------------------------------------- /src/catalog.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | 8 | #include "ppconsul/catalog.h" 9 | #include "s11n_types.h" 10 | 11 | 12 | namespace json11 { 13 | void load(const json11::Json& src, ppconsul::catalog::NodeService& dst) 14 | { 15 | using ppconsul::s11n::load; 16 | 17 | load(src, dst.first); 18 | load(src, dst.second.id, "ServiceID"); 19 | load(src, dst.second.name, "ServiceName"); 20 | load(src, dst.second.address, "ServiceAddress"); 21 | load(src, dst.second.port, "ServicePort"); 22 | load(src, dst.second.tags, "ServiceTags"); 23 | load(src, dst.second.meta, "ServiceMeta"); 24 | } 25 | 26 | void load(const json11::Json& src, ppconsul::catalog::NodeServices& dst) 27 | { 28 | using ppconsul::s11n::load; 29 | 30 | load(src, dst.first, "Node"); 31 | load(src, dst.second, "Services"); 32 | } 33 | } 34 | 35 | namespace ppconsul { namespace catalog { 36 | 37 | namespace impl { 38 | 39 | StringList parseDatacenters(const std::string& json) 40 | { 41 | return s11n::parseJson(json); 42 | } 43 | 44 | std::vector parseNodes(const std::string& json) 45 | { 46 | return s11n::parseJson>(json); 47 | } 48 | 49 | NodeServices parseNode(const std::string& json) 50 | { 51 | return s11n::parseJson(json); 52 | } 53 | 54 | std::map parseServices(const std::string& json) 55 | { 56 | return s11n::parseJson>(json); 57 | } 58 | 59 | std::vector parseService(const std::string& json) 60 | { 61 | return s11n::parseJson>(json); 62 | } 63 | 64 | } 65 | }} 66 | -------------------------------------------------------------------------------- /src/sessions.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | 8 | #include "ppconsul/sessions.h" 9 | #include "s11n.h" 10 | 11 | namespace ppconsul { namespace sessions { 12 | 13 | struct CreateResult 14 | { 15 | std::string id; 16 | }; 17 | 18 | void load(const s11n::Json& src, CreateResult& dst) 19 | { 20 | using s11n::load; 21 | 22 | load(src, dst.id, "ID"); 23 | } 24 | 25 | std::string encodeBehavior(InvalidationBehavior behavior) 26 | { 27 | switch (behavior) { 28 | case InvalidationBehavior::Release: 29 | return "release"; 30 | case InvalidationBehavior::Delete: 31 | return "delete"; 32 | } 33 | return {}; 34 | } 35 | 36 | namespace impl { 37 | using s11n::to_json; 38 | 39 | std::string createBodyJson(const std::string &name, const std::string &node, std::chrono::seconds lockDelay, 40 | InvalidationBehavior behavior, std::chrono::seconds ttl) 41 | { 42 | s11n::Json::object obj{ 43 | {"Behavior", encodeBehavior(behavior)}, 44 | }; 45 | 46 | if (lockDelay.count() >= 0) 47 | obj["LockDelay"] = to_json(lockDelay); 48 | 49 | if (!name.empty()) 50 | obj["Name"] = name; 51 | 52 | if (!node.empty()) 53 | obj["Node"] = node; 54 | 55 | if (ttl.count() >= 0) 56 | obj["TTL"] = to_json(ttl); 57 | 58 | return s11n::Json(std::move(obj)).dump(); 59 | } 60 | 61 | std::string parseCreateResponse(const std::string &resp) 62 | { 63 | return s11n::parseJson(resp).id; 64 | } 65 | 66 | } 67 | }} 68 | -------------------------------------------------------------------------------- /src/s11n_types.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | 8 | #include "ppconsul/types.h" 9 | #include "s11n.h" 10 | 11 | 12 | namespace ppconsul { 13 | 14 | inline void load(const s11n::Json& src, ServiceInfo& dst) 15 | { 16 | using s11n::load; 17 | 18 | load(src, dst.id, "ID"); 19 | load(src, dst.name, "Service"); 20 | load(src, dst.address, "Address"); 21 | load(src, dst.port, "Port"); 22 | load(src, dst.tags, "Tags"); 23 | load(src, dst.meta, "Meta"); 24 | } 25 | 26 | inline void load(const s11n::Json& src, Node& dst) 27 | { 28 | using s11n::load; 29 | 30 | load(src, dst.node, "Node"); 31 | load(src, dst.address, "Address"); 32 | } 33 | 34 | inline void load(const s11n::Json& src, CheckStatus& dst) 35 | { 36 | const auto& s = src.string_value(); 37 | 38 | if (s == "passing") 39 | dst = CheckStatus::Passing; 40 | else if (s == "warning") 41 | dst = CheckStatus::Warning; 42 | else if (s == "critical") 43 | dst = CheckStatus::Critical; 44 | else 45 | dst = CheckStatus::Unknown; 46 | } 47 | 48 | 49 | inline void load(const s11n::Json& src, CheckInfo& dst) 50 | { 51 | using s11n::load; 52 | 53 | load(src, dst.id, "CheckID"); 54 | load(src, dst.node, "Node"); 55 | load(src, dst.name, "Name"); 56 | load(src, dst.status, "Status"); 57 | load(src, dst.notes, "Notes"); 58 | load(src, dst.output, "Output"); 59 | load(src, dst.serviceId, "ServiceID"); 60 | load(src, dst.serviceName, "ServiceName"); 61 | } 62 | 63 | inline void load(const json11::Json& src, Coordinate& dst) 64 | { 65 | using s11n::load; 66 | 67 | load(src, dst.adjustment, "Adjustment"); 68 | load(src, dst.error, "Error"); 69 | load(src, dst.height, "Height"); 70 | load(src, dst.vec, "Vec"); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /tests/unittests/helpers.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include "ppconsul/helpers.h" 8 | #include 9 | 10 | 11 | TEST_CASE("helpers.base64decode", "[base64]") 12 | { 13 | using ppconsul::helpers::decodeBase64; 14 | 15 | CHECK("" == decodeBase64("")); 16 | CHECK("sure." == decodeBase64("c3VyZS4=")); 17 | CHECK("sure." == decodeBase64("c3VyZS4")); 18 | CHECK("sure" == decodeBase64("c3VyZQ==")); 19 | CHECK("sure" == decodeBase64("c3VyZQ")); 20 | CHECK("sur" == decodeBase64("c3Vy")); 21 | CHECK("su" == decodeBase64("c3U=")); 22 | CHECK("s" == decodeBase64("cw==")); 23 | CHECK("bla-bl" == decodeBase64("YmxhLWJs")); 24 | CHECK("bla-bla" == decodeBase64("YmxhLWJsYQ==")); 25 | CHECK("bla-bla" == decodeBase64("\x10YmxhLWJsYQ==")); 26 | 27 | CHECK("bla-bla" == decodeBase64("YmxhLWJ\x10\x15sYQ==")); 28 | CHECK_NOTHROW(decodeBase64("Ym\x15xhL\x0WJsYQ==")); // Not a real test, just be sure that it's not fail 29 | } 30 | 31 | TEST_CASE("helpers.base64encode", "[base64]") 32 | { 33 | using ppconsul::helpers::encodeBase64; 34 | 35 | CHECK(encodeBase64("") == ""); 36 | CHECK(encodeBase64("sure.") == "c3VyZS4="); 37 | CHECK(encodeBase64("sure") == "c3VyZQ=="); 38 | CHECK(encodeBase64("sur") == "c3Vy"); 39 | CHECK(encodeBase64("su") == "c3U="); 40 | CHECK(encodeBase64("s") == "cw=="); 41 | CHECK(encodeBase64("bla-bl") == "YmxhLWJs"); 42 | CHECK(encodeBase64("bla-bla") == "YmxhLWJsYQ=="); 43 | 44 | { 45 | const auto ntriples = 22000; 46 | CHECK( 47 | encodeBase64( 48 | std::string(ntriples * 3, '\0')) == 49 | std::string(ntriples * 4, 'A')); 50 | } 51 | } 52 | 53 | TEST_CASE("helpers.url encode", "[url]") 54 | { 55 | using ppconsul::helpers::encodeUrl; 56 | 57 | CHECK(encodeUrl("") == ""); 58 | CHECK(encodeUrl("\x01\x13 bla /* {tag}%") == "%01%13%20bla%20%2F%2A%20%7Btag%7D%25"); 59 | CHECK(encodeUrl("\x7F\x80\x81.-_~") == "%7F%80%81.-_~"); 60 | CHECK(encodeUrl({"\x0?&", 3}) == "%00%3F%26"); 61 | } 62 | -------------------------------------------------------------------------------- /include/ppconsul/error.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | namespace ppconsul { 15 | 16 | class Error: public std::exception {}; 17 | 18 | class FormatError: public Error 19 | { 20 | public: 21 | FormatError(std::string message) 22 | : m_message(std::move(message)) 23 | {} 24 | 25 | virtual const char *what() const noexcept override { return m_message.c_str(); } 26 | 27 | private: 28 | std::string m_message; 29 | }; 30 | 31 | class OperationAborted: public Error 32 | { 33 | public: 34 | OperationAborted() = default; 35 | 36 | virtual const char *what() const noexcept override { return "Operation aborted"; } 37 | }; 38 | 39 | class RequestTimedOut: public Error 40 | { 41 | public: 42 | RequestTimedOut(std::string message) 43 | : m_message(std::move(message)) 44 | {} 45 | 46 | virtual const char *what() const noexcept override { return m_message.c_str(); } 47 | 48 | private: 49 | std::string m_message; 50 | }; 51 | 52 | class BadStatus: public Error 53 | { 54 | public: 55 | explicit BadStatus(http::Status status, std::string message = "") 56 | : m_status(std::move(status)) 57 | , m_message(std::move(message)) 58 | {} 59 | 60 | int code() const noexcept{ return m_status.code(); } 61 | 62 | const http::Status& status() const noexcept{ return m_status; } 63 | const std::string& message() const noexcept{ return m_message; } 64 | 65 | virtual const char *what() const noexcept override; 66 | 67 | private: 68 | http::Status m_status; 69 | std::string m_message; 70 | mutable std::string m_what; 71 | }; 72 | 73 | class NotFoundError: public BadStatus 74 | { 75 | public: 76 | enum { Code = 404 }; 77 | 78 | /*explicit NotFoundError(http::HttpStatus status, std::string message = "") 79 | : BadStatus(std::move(status), std::move(message)) 80 | {}*/ 81 | 82 | NotFoundError() 83 | : BadStatus(http::Status(Code, "Not Found")) 84 | {} 85 | }; 86 | 87 | void throwStatusError(http::Status status, std::string data = ""); 88 | 89 | } 90 | -------------------------------------------------------------------------------- /include/ppconsul/http/http_client.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | namespace ppconsul { namespace http { 18 | const std::string Token_Header_Name("X-Consul-Token"); 19 | 20 | struct TlsConfig 21 | { 22 | std::string cert; 23 | std::string certType; 24 | std::string key; 25 | std::string keyType; 26 | std::string caPath; 27 | std::string caInfo; 28 | bool verifyPeer; 29 | bool verifyHost; 30 | bool verifyStatus; 31 | 32 | // Note that keyPass is c-str rather than std::string. That's to make it possible 33 | // for the caller to keep the actual password in a secure way like in a wipe after use memory block 34 | // and avoid copying the secret around. The pointer must be valid until `Consul` object that uses it is deleted. 35 | const char *keyPass; 36 | }; 37 | 38 | struct HttpClientConfig 39 | { 40 | std::chrono::milliseconds connectTimeout; 41 | std::chrono::milliseconds requestTimeout; // 0 means no timeout 42 | 43 | TlsConfig tls; 44 | }; 45 | 46 | using RequestHeaders = std::map; 47 | 48 | class HttpClient 49 | { 50 | public: 51 | using GetResponse = std::tuple; 52 | using PutResponse = std::pair; 53 | using DelResponse = std::pair; 54 | 55 | // Returns {HttpStatus, headers, body} 56 | virtual GetResponse get(const std::string& path, const std::string& query, 57 | const RequestHeaders & headers = RequestHeaders{}) = 0; 58 | 59 | // Returns {HttpStatus, body} 60 | virtual PutResponse put(const std::string& path, const std::string& query, const std::string& data, 61 | const RequestHeaders & headers = RequestHeaders{}) = 0; 62 | 63 | // Returns {HttpStatus, body} 64 | virtual DelResponse del(const std::string& path, const std::string& query, 65 | const RequestHeaders & headers = RequestHeaders{}) = 0; 66 | 67 | virtual ~HttpClient() {}; 68 | }; 69 | 70 | }} 71 | -------------------------------------------------------------------------------- /ext/b64/cencode.c: -------------------------------------------------------------------------------- 1 | /* 2 | cencoder.c - c source to a base64 encoding algorithm implementation 3 | 4 | This is part of the libb64 project, and has been placed in the public domain. 5 | For details, see http://sourceforge.net/projects/libb64 6 | */ 7 | 8 | #include 9 | 10 | void base64_init_encodestate(base64_encodestate* state_in) 11 | { 12 | state_in->step = step_A; 13 | state_in->result = 0; 14 | } 15 | 16 | char base64_encode_value(char value_in) 17 | { 18 | static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 19 | if (value_in > 63) return '='; 20 | return encoding[(int)value_in]; 21 | } 22 | 23 | int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) 24 | { 25 | const char* plainchar = plaintext_in; 26 | const char* const plaintextend = plaintext_in + length_in; 27 | char* codechar = code_out; 28 | char result; 29 | char fragment; 30 | 31 | result = state_in->result; 32 | 33 | switch (state_in->step) 34 | { 35 | while (1) 36 | { 37 | case step_A: 38 | if (plainchar == plaintextend) 39 | { 40 | state_in->result = result; 41 | state_in->step = step_A; 42 | return codechar - code_out; 43 | } 44 | fragment = *plainchar++; 45 | result = (fragment & 0x0fc) >> 2; 46 | *codechar++ = base64_encode_value(result); 47 | result = (fragment & 0x003) << 4; 48 | case step_B: 49 | if (plainchar == plaintextend) 50 | { 51 | state_in->result = result; 52 | state_in->step = step_B; 53 | return codechar - code_out; 54 | } 55 | fragment = *plainchar++; 56 | result |= (fragment & 0x0f0) >> 4; 57 | *codechar++ = base64_encode_value(result); 58 | result = (fragment & 0x00f) << 2; 59 | case step_C: 60 | if (plainchar == plaintextend) 61 | { 62 | state_in->result = result; 63 | state_in->step = step_C; 64 | return codechar - code_out; 65 | } 66 | fragment = *plainchar++; 67 | result |= (fragment & 0x0c0) >> 6; 68 | *codechar++ = base64_encode_value(result); 69 | result = (fragment & 0x03f) >> 0; 70 | *codechar++ = base64_encode_value(result); 71 | } 72 | } 73 | /* control should not reach here */ 74 | return codechar - code_out; 75 | } 76 | 77 | int base64_encode_blockend(char* code_out, base64_encodestate* state_in) 78 | { 79 | char* codechar = code_out; 80 | 81 | switch (state_in->step) 82 | { 83 | case step_B: 84 | *codechar++ = base64_encode_value(state_in->result); 85 | *codechar++ = '='; 86 | *codechar++ = '='; 87 | break; 88 | case step_C: 89 | *codechar++ = base64_encode_value(state_in->result); 90 | *codechar++ = '='; 91 | break; 92 | case step_A: 93 | break; 94 | } 95 | 96 | return codechar - code_out; 97 | } 98 | 99 | -------------------------------------------------------------------------------- /include/ppconsul/response.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #pragma once 8 | 9 | #include "ppconsul/config.h" 10 | #include 11 | #include 12 | 13 | 14 | namespace ppconsul { 15 | 16 | struct ResponseHeaders 17 | { 18 | // Creates non-valid headers (i.e. !valid()) 19 | ResponseHeaders() 20 | : ResponseHeaders(0, false, std::chrono::milliseconds::zero()) 21 | {} 22 | 23 | ResponseHeaders(uint64_t index, bool knownLeader, const std::chrono::milliseconds& lastContact) 24 | : m_index(index) 25 | , m_knownLeader(knownLeader) 26 | , m_lastContact(lastContact) 27 | {} 28 | 29 | bool valid() const { return 0 != m_index; } 30 | explicit operator bool () const { return valid(); } 31 | 32 | uint64_t index() const { return m_index; } 33 | bool knownLeader() const { return m_knownLeader; } 34 | const std::chrono::milliseconds& lastContact() const { return m_lastContact; } 35 | 36 | uint64_t m_index; 37 | bool m_knownLeader; 38 | std::chrono::milliseconds m_lastContact; 39 | }; 40 | 41 | template 42 | struct Response 43 | { 44 | public: 45 | using DataType = Data; 46 | 47 | Response() 48 | {} 49 | 50 | Response(const ResponseHeaders& headers) 51 | : m_headers(headers) 52 | {} 53 | 54 | Response(const ResponseHeaders& headers, const DataType& data) 55 | : m_value(data) 56 | , m_headers(headers) 57 | {} 58 | 59 | Response(const ResponseHeaders& headers, DataType&& data) 60 | : m_value(std::move(data)) 61 | , m_headers(headers) 62 | {} 63 | 64 | ResponseHeaders& headers() { return m_headers; } 65 | const ResponseHeaders& headers() const { return m_headers; } 66 | 67 | void headers(const ResponseHeaders& headers) { m_headers = headers; } 68 | 69 | DataType& data() { return m_value; } 70 | const DataType& data() const { return m_value; } 71 | 72 | void data(const DataType& data) { m_value = data; } 73 | void data(DataType&& data) { m_value = std::move(data); } 74 | 75 | private: 76 | DataType m_value; 77 | ResponseHeaders m_headers; 78 | }; 79 | 80 | template 81 | Response makeResponse(const ResponseHeaders& headers, DataType&& data) 82 | { 83 | return Response(headers, std::forward(data)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /status.md: -------------------------------------------------------------------------------- 1 | # HTTP API v1 Implementation Status 2 | 3 | The status of Consul HTTP API v1 coverage. The API is extending on each Consul release and doesn't have a precise versioning thus I'm trying to keep this document syncronized with the latest Consul documentation (https://www.consul.io/docs/agent/http.html). At the moment, the document reflects **Consul 0.7.2** HTTP API. 4 | 5 | Note that all implemented features are tested with automated integration tests. 6 | 7 | ## General 8 | 9 | ### Blocking Queries 10 | 11 | Blocking query feature is supported in a straitforward way: when API that supports blocking queries is called with keyword `block_for=` then the call blocks. There is no smart things like async non-block handling with calbacks, backgound polling etc. If you have any ideas/suggerstions of a convenient interface to work with blocking queries, you are welcome. 12 | 13 | ## Endpoints 14 | 15 | ### ACLs 16 | 17 | TBD 18 | 19 | ### Agent 20 | 21 | Endpoint is supported except the following: 22 | - ACL tokens 23 | - Endpoint [`/v1/agent/reload`](https://www.consul.io/docs/agent/http/agent.html#agent_reload) 24 | - Endpoint [`/v1/agent/monitor`](https://www.consul.io/docs/agent/http/agent.html#agent_monitor) 25 | - Endpoint [`/v1/agent/leave`](https://www.consul.io/docs/agent/http/agent.html#agent_leave) 26 | - Endpoint [`/v1/agent/maintenance`](https://www.consul.io/docs/agent/http/agent.html#agent_maintenance) 27 | - Endpoint [`/v1/agent/service/maintenance`](https://www.consul.io/docs/agent/http/agent.html#agent_service_maintenance) 28 | - Specify an initial status for checks (`"Status"` field) 29 | - Multiple checks associated with a single service. 30 | - Accessing to `"Config"` and `"Stats"` objects received from `/v1/agent/self` endpoint. There is no plan to support it any time soon unless requested by users. 31 | 32 | ### Catalog 33 | 34 | Endpoint is supported except the following: 35 | - ACL tokens 36 | - Parameter `?near=` for querying nodes and services 37 | - Registration/deregistration. Please use the agent endpoint instead. There is no plan to support it any time soon unless requested by users. 38 | 39 | ### Coordinates 40 | 41 | TBD 42 | 43 | ### Events 44 | 45 | TBD 46 | 47 | ### Health 48 | 49 | Endpoint support is in progress (code is here but not tests yet) 50 | 51 | Not supported: 52 | - ACL tokens 53 | 54 | ### KV Store 55 | 56 | Endpoint is supported except the following: 57 | - ACL tokens 58 | 59 | At the moment it's only possible to work with values as strings, but there is a plan to add a typed interface, something like `get(key)`, `put(key, some_double)`, etc. 60 | 61 | ### Operator 62 | 63 | TBD 64 | 65 | ### Prepared Query 66 | 67 | TBD 68 | 69 | ### Sessions 70 | 71 | TBD 72 | 73 | ### Snapshots 74 | 75 | TBD 76 | 77 | ### Status 78 | 79 | Endpoint is supported 80 | 81 | ### Transactions 82 | 83 | TBD 84 | -------------------------------------------------------------------------------- /ext/b64/cdecode.c: -------------------------------------------------------------------------------- 1 | /* 2 | cdecoder.c - c source to a base64 decoding algorithm implementation 3 | 4 | This is part of the libb64 project, and has been placed in the public domain. 5 | For details, see http://sourceforge.net/projects/libb64 6 | */ 7 | 8 | #include 9 | 10 | int base64_decode_value(char value_in) 11 | { 12 | static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; 13 | static const char decoding_size = sizeof(decoding); 14 | value_in -= 43; 15 | if (value_in < 0 || value_in >= decoding_size) return -1; 16 | return decoding[(int)value_in]; 17 | } 18 | 19 | void base64_init_decodestate(base64_decodestate* state_in) 20 | { 21 | state_in->step = step_a; 22 | state_in->plainchar = 0; 23 | } 24 | 25 | int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in) 26 | { 27 | const char* codechar = code_in; 28 | char* plainchar = plaintext_out; 29 | char fragment; 30 | 31 | *plainchar = state_in->plainchar; 32 | 33 | switch (state_in->step) 34 | { 35 | while (1) 36 | { 37 | case step_a: 38 | do { 39 | if (codechar == code_in+length_in) 40 | { 41 | state_in->step = step_a; 42 | state_in->plainchar = *plainchar; 43 | return plainchar - plaintext_out; 44 | } 45 | fragment = (char)base64_decode_value(*codechar++); 46 | } while (fragment < 0); 47 | *plainchar = (fragment & 0x03f) << 2; 48 | case step_b: 49 | do { 50 | if (codechar == code_in+length_in) 51 | { 52 | state_in->step = step_b; 53 | state_in->plainchar = *plainchar; 54 | return plainchar - plaintext_out; 55 | } 56 | fragment = (char)base64_decode_value(*codechar++); 57 | } while (fragment < 0); 58 | *plainchar++ |= (fragment & 0x030) >> 4; 59 | *plainchar = (fragment & 0x00f) << 4; 60 | case step_c: 61 | do { 62 | if (codechar == code_in+length_in) 63 | { 64 | state_in->step = step_c; 65 | state_in->plainchar = *plainchar; 66 | return plainchar - plaintext_out; 67 | } 68 | fragment = (char)base64_decode_value(*codechar++); 69 | } while (fragment < 0); 70 | *plainchar++ |= (fragment & 0x03c) >> 2; 71 | *plainchar = (fragment & 0x003) << 6; 72 | case step_d: 73 | do { 74 | if (codechar == code_in+length_in) 75 | { 76 | state_in->step = step_d; 77 | state_in->plainchar = *plainchar; 78 | return plainchar - plaintext_out; 79 | } 80 | fragment = (char)base64_decode_value(*codechar++); 81 | } while (fragment < 0); 82 | *plainchar++ |= (fragment & 0x03f); 83 | } 84 | } 85 | /* control should not reach here */ 86 | return plainchar - plaintext_out; 87 | } 88 | 89 | -------------------------------------------------------------------------------- /tests/unittests/parameters.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include "ppconsul/parameters.h" 8 | #include 9 | 10 | 11 | namespace { 12 | enum class Enum { Default, Val1, Val2 }; 13 | } 14 | 15 | namespace test_params { 16 | PPCONSUL_KEYWORD(p1, std::string) 17 | PPCONSUL_KEYWORD(p2, uint64_t) 18 | PPCONSUL_KEYWORD_NAMED_(p3, bool, "boolean") 19 | KWARGS_KEYWORD(p4, Enum) 20 | 21 | inline void printParameter(std::ostream& os, Enum v, KWARGS_KW_TAG(p4)) 22 | { 23 | switch (v) 24 | { 25 | case Enum::Val1: os << "val1"; break; 26 | case Enum::Val2: os << "val2"; break; 27 | default: break; 28 | } 29 | } 30 | } 31 | 32 | 33 | TEST_CASE("makeQuery", "[parameters]") 34 | { 35 | using ppconsul::parameters::makeQuery; 36 | using ppconsul::parameters::makeUrl; 37 | 38 | using namespace test_params; 39 | 40 | CHECK(makeQuery() == ""); 41 | 42 | CHECK(makeUrl("http://www.example.com/something/interesting") == 43 | "http://www.example.com/something/interesting"); 44 | 45 | CHECK(makeUrl("/something/interesting") == 46 | "/something/interesting"); 47 | 48 | CHECK(makeUrl("/something/interesting", p1 = "") == 49 | "/something/interesting"); 50 | 51 | CHECK(makeUrl("http://www.example.com/something/interesting", p1 = "bla", p2 = 99, p1 = "wow") == 52 | "http://www.example.com/something/interesting?p2=99&p1=wow"); 53 | 54 | CHECK(makeUrl("/something/interesting", p1 = "bla", p2 = 99, p1 = "wow") == 55 | "/something/interesting?p2=99&p1=wow"); 56 | 57 | CHECK(makeUrl("/something/interesting", p1 = "bla", p2 = 99, p1 = "") == 58 | "/something/interesting?p2=99"); 59 | 60 | CHECK(makeUrl("/something/interesting", p3 = true) == 61 | "/something/interesting?boolean=1"); 62 | 63 | CHECK(makeUrl("/something/interesting", p3 = false) == 64 | "/something/interesting?boolean=0"); 65 | 66 | CHECK(makeUrl("/something/interesting", p3 = false, p3 = true) == 67 | "/something/interesting?boolean=1"); 68 | 69 | CHECK(makeUrl("/something/interesting", p4 = Enum::Val1) == 70 | "/something/interesting?val1"); 71 | 72 | CHECK(makeUrl("/something/interesting", p4 = Enum::Default) == 73 | "/something/interesting"); 74 | 75 | CHECK(makeUrl("/something/interesting", p4 = Enum::Default, p1 = "bla") == 76 | "/something/interesting?p1=bla"); 77 | 78 | CHECK(makeUrl("/something/interesting", p2=2, p4 = Enum::Val1, p3 = true, p1 = "bla") == 79 | "/something/interesting?p2=2&val1&boolean=1&p1=bla"); 80 | } 81 | -------------------------------------------------------------------------------- /include/ppconsul/parameters.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #pragma once 8 | 9 | #include "kwargs.h" 10 | #include "helpers.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | #define PPCONSUL_KEYWORD_NAMED_(keyword, type, name_) \ 18 | KWARGS_KEYWORD(keyword, type) \ 19 | inline const char *parameter_name(KWARGS_KW_TAG(keyword)) { return name_; } 20 | 21 | #define PPCONSUL_KEYWORD(keyword, type) PPCONSUL_KEYWORD_NAMED_(keyword, type, BOOST_PP_STRINGIZE(keyword)) 22 | 23 | 24 | namespace ppconsul { namespace parameters { 25 | 26 | namespace detail { 27 | template 28 | void printParameter(std::ostream&os, const Value& v, Keyword k) 29 | { 30 | os << parameter_name(k) << "=" << v; 31 | } 32 | 33 | template 34 | void printParameter(std::ostream&os, const std::string& v, Keyword k) 35 | { 36 | if (!v.empty()) 37 | os << parameter_name(k) << "=" << helpers::encodeUrl(v); 38 | } 39 | 40 | struct ParameterPrinter 41 | { 42 | ParameterPrinter(std::ostream& os): m_os(os) {} 43 | 44 | template 45 | void operator() (const T& t) const 46 | { 47 | auto pos = m_os.tellp(); 48 | printParameter(m_os, kwargs::get_value(t), kwargs::get_keyword(t)); 49 | if (m_os.tellp() != pos) 50 | m_os << '&'; 51 | } 52 | 53 | private: 54 | std::ostream& m_os; 55 | }; 56 | } 57 | 58 | inline std::string makeQuery() 59 | { 60 | return{}; 61 | } 62 | 63 | template> 64 | std::string makeQuery(const Params&... params) 65 | { 66 | std::ostringstream os; 67 | 68 | const auto p = kwargs::unique_args(params...); 69 | boost::fusion::for_each(p, detail::ParameterPrinter(os)); 70 | 71 | // Remove last '&' if exists 72 | auto r = os.str(); 73 | if (!r.empty() && r.back() == '&') 74 | r.resize(r.size() - 1); 75 | return r; 76 | } 77 | 78 | template> 79 | std::string makeUrl(const std::string& path, const Params&... params) 80 | { 81 | auto query = makeQuery(params...); 82 | 83 | std::string r; 84 | r.reserve(path.size() + query.size() + 1); // 1 is for query prefix '?' 85 | 86 | r += path; 87 | if (!query.empty()) 88 | { 89 | r += '?'; 90 | r += query; 91 | } 92 | 93 | return r; 94 | } 95 | }} 96 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2020 Andrey Upadyshev 2 | # 3 | # Use, modification and distribution are subject to the 4 | # Boost Software License, Version 1.0. (See accompanying file 5 | # LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | project(ppconsul) 8 | 9 | set(HEADERS 10 | agent.h 11 | catalog.h 12 | consul.h 13 | coordinate.h 14 | error.h 15 | health.h 16 | helpers.h 17 | client_pool.h 18 | kv.h 19 | kwargs.h 20 | parameters.h 21 | ppconsul.h 22 | response.h 23 | sessions.h 24 | types.h 25 | http/http_client.h 26 | http/status.h 27 | ) 28 | 29 | set(SOURCES 30 | http_helpers.h 31 | s11n.h 32 | s11n_types.h 33 | agent.cpp 34 | catalog.cpp 35 | consul.cpp 36 | coordinate.cpp 37 | helpers.cpp 38 | health.cpp 39 | client_pool.cpp 40 | kv.cpp 41 | sessions.cpp 42 | status.cpp 43 | ) 44 | 45 | list(APPEND SOURCES "curl/http_client.h") 46 | list(APPEND SOURCES "curl/http_client.cpp") 47 | 48 | foreach(SRC ${HEADERS}) 49 | list(APPEND SOURCES "${HEADERS_DIR}/${SRC}") 50 | endforeach() 51 | 52 | if (BUILD_STATIC_LIB) 53 | add_library(${PROJECT_NAME} STATIC 54 | ${SOURCES} 55 | ${LIBB64_SOURCES} 56 | ) 57 | else() 58 | add_library(${PROJECT_NAME} SHARED 59 | ${SOURCES} 60 | ${LIBB64_SOURCES} 61 | ) 62 | endif() 63 | 64 | target_compile_features(${PROJECT_NAME} PUBLIC cxx_auto_type cxx_decltype cxx_static_assert cxx_rvalue_references) 65 | 66 | target_include_directories(${PROJECT_NAME} 67 | PUBLIC 68 | $ 69 | $ 70 | $ 71 | PRIVATE 72 | ${CMAKE_CURRENT_SOURCE_DIR} 73 | ${Boost_INCLUDE_DIRS} 74 | ${CMAKE_CURRENT_SOURCE_DIR}/../ext 75 | ) 76 | 77 | target_link_libraries(${PROJECT_NAME} 78 | PRIVATE 79 | json11 80 | ${Boost_LIBRARIES} 81 | ) 82 | 83 | target_include_directories(${PROJECT_NAME} PRIVATE ${CURL_INCLUDE_DIR}) 84 | target_link_libraries(${PROJECT_NAME} PRIVATE ${CURL_LIBRARIES}) 85 | 86 | source_group(${PROJECT_NAME} FILES ${SOURCES}) 87 | source_group(libb64 FILES ${LIBB64_SOURCES}) 88 | 89 | set_target_properties(${PROJECT_NAME} PROPERTIES 90 | VERSION ${Ppconsul_VERSION} 91 | SOVERSION "${Ppconsul_VERSION_MAJOR}.${Ppconsul_VERSION_MINOR}" 92 | COMPILE_PDB_NAME ${PROJECT_NAME} 93 | COMPILE_PDB_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} 94 | FOLDER ${PROJECT_NAME} 95 | ) 96 | 97 | install( 98 | TARGETS ${PROJECT_NAME} 99 | EXPORT ${CMAKE_PROJECT_NAME} 100 | LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" 101 | ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" 102 | RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" 103 | ) 104 | if(WIN32) 105 | install( 106 | FILES ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${PROJECT_NAME}.pdb 107 | DESTINATION "${CMAKE_INSTALL_LIBDIR}" 108 | OPTIONAL 109 | ) 110 | endif() 111 | 112 | -------------------------------------------------------------------------------- /include/ppconsul/types.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #pragma once 8 | 9 | #include "ppconsul/config.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | namespace ppconsul { 23 | 24 | enum class Consistency 25 | { 26 | Default, 27 | Consistent, 28 | Stale 29 | }; 30 | 31 | using duration = std::chrono::milliseconds; 32 | 33 | using BlockForValue = std::pair; 34 | 35 | using Tags = std::set; 36 | 37 | using Metadata = std::map; 38 | 39 | using StringList = std::vector; 40 | 41 | struct Node 42 | { 43 | std::string node; 44 | std::string address; 45 | 46 | bool valid() const { return !node.empty() && !address.empty(); } 47 | }; 48 | 49 | enum class CheckStatus 50 | { 51 | Unknown, // Not used since Consul 0.4.1 52 | Passing, 53 | Warning, 54 | Critical 55 | }; 56 | 57 | struct CheckInfo 58 | { 59 | std::string id; 60 | std::string name; 61 | std::string notes; 62 | std::string serviceId; 63 | std::string serviceName; 64 | std::string node; 65 | CheckStatus status; 66 | std::string output; 67 | }; 68 | 69 | struct ServiceInfo 70 | { 71 | std::string id; 72 | std::string name; 73 | std::string address; 74 | uint16_t port; 75 | Tags tags; 76 | Metadata meta; 77 | }; 78 | 79 | struct Coordinate 80 | { 81 | double adjustment; 82 | double error; 83 | double height; 84 | std::vector vec; 85 | }; 86 | 87 | struct WithHeaders {}; 88 | const WithHeaders withHeaders = WithHeaders(); 89 | 90 | inline std::ostream& operator<< (std::ostream& os, const CheckStatus& s) 91 | { 92 | switch (s) 93 | { 94 | case CheckStatus::Passing: 95 | os << "passing"; 96 | break; 97 | case CheckStatus::Warning: 98 | os << "warning"; 99 | break; 100 | case CheckStatus::Critical: 101 | os << "critical"; 102 | break; 103 | case CheckStatus::Unknown: 104 | os << "unknown"; 105 | break; 106 | default: 107 | os << "?"; 108 | break; 109 | } 110 | 111 | return os; 112 | } 113 | 114 | inline bool operator!= (const Node& l, const Node& r) 115 | { 116 | return l.node != r.node || l.address != r.address; 117 | } 118 | 119 | inline bool operator== (const Node& l, const Node& r) 120 | { 121 | return !operator!=(l, r); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/helpers.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include "ppconsul/helpers.h" 8 | #include "s11n.h" 9 | 10 | extern "C" { 11 | #include 12 | #include 13 | } 14 | 15 | 16 | namespace ppconsul { namespace helpers { 17 | 18 | namespace { 19 | inline bool is_char_unsafe(char c) 20 | { 21 | switch (c) 22 | { 23 | // Reserved characters 24 | case '!': case '#': case '$': case '&': case '\'': 25 | case '(': case ')': case '*': case '+': case ',': 26 | case '/': case ':': case ';': case '=': case '?': 27 | case '@': case '[': case ']': 28 | // Other characters 29 | case '\x20': case '\x7F': 30 | case '"': case '%' : /*case '-':*/ /*case '.':*/ case '<': 31 | case '>': case '\\': case '^': /*case '_':*/ case '`': 32 | case '{': case '|' : case '}': /*case '~':*/ 33 | return true; 34 | default: 35 | return static_cast(c) < 0x20 || static_cast(c) >= 0x80; 36 | } 37 | } 38 | 39 | inline char to_hex(unsigned char c) 40 | { 41 | char table[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 42 | return table[c]; 43 | } 44 | } 45 | 46 | std::string decodeBase64(const std::string& s) 47 | { 48 | std::string r; 49 | if (s.empty()) 50 | return r; 51 | r.resize((s.size() + 3) / 4 * 3); 52 | base64_decodestate state; 53 | base64_init_decodestate(&state); 54 | auto len = base64_decode_block(s.data(), s.size(), &r.front(), &state); 55 | r.resize(static_cast(len)); 56 | return r; 57 | } 58 | 59 | std::string encodeBase64(const std::string &s) 60 | { 61 | std::string r; 62 | if (s.empty()) 63 | return r; 64 | r.resize(s.size() * 4 / 3 + 10); 65 | base64_encodestate state; 66 | base64_init_encodestate(&state); 67 | auto len = base64_encode_block(s.data(), s.size(), &r.front(), &state); 68 | len += base64_encode_blockend(&r.front() + len, &state); 69 | if (len && r[len - 1] == '\n') 70 | --len; 71 | r.resize(static_cast(len)); 72 | return r; 73 | } 74 | 75 | std::string encodeUrl(const std::string&s) 76 | { 77 | std::string r; 78 | r.reserve(s.size()); 79 | 80 | for (const auto c: s) 81 | { 82 | if (is_char_unsafe(c)) 83 | { 84 | r += '%'; 85 | r += to_hex(static_cast(c) >> 4); 86 | r += to_hex(static_cast(c) & 0xF); 87 | } 88 | else 89 | { 90 | r += c; 91 | } 92 | } 93 | 94 | return r; 95 | } 96 | 97 | bool parseJsonBool(const std::string& s) 98 | { 99 | return s11n::parseJson(s); 100 | } 101 | }} 102 | -------------------------------------------------------------------------------- /tests/chrono_io.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | namespace chrono_io_detail 10 | { 11 | template struct native_period: std::false_type {}; 12 | 13 | template<> struct native_period: std::true_type {}; 14 | template<> struct native_period: std::true_type {}; 15 | template<> struct native_period: std::true_type {}; 16 | template<> struct native_period: std::true_type {}; 17 | template<> struct native_period: std::true_type {}; 18 | template<> struct native_period: std::true_type {}; 19 | 20 | 21 | template 22 | struct period_name; 23 | 24 | template<> struct period_name 25 | { 26 | template static const CharT* name() 27 | { 28 | static CharT s[] = {'n', 's' , 0}; 29 | return s; 30 | } 31 | }; 32 | 33 | template<> struct period_name 34 | { 35 | template static const CharT* name() 36 | { 37 | static CharT s[] = { 'u', 's', 0 }; 38 | return s; 39 | } 40 | }; 41 | 42 | template<> struct period_name 43 | { 44 | template static const CharT* name() 45 | { 46 | static CharT s[] = { 'm', 's', 0 }; 47 | return s; 48 | } 49 | }; 50 | 51 | template<> struct period_name 52 | { 53 | template static const CharT* name() 54 | { 55 | static CharT s[] = { 's', 0 }; 56 | return s; 57 | } 58 | }; 59 | 60 | template<> struct period_name 61 | { 62 | template static const CharT* name() 63 | { 64 | static CharT s[] = { 'm', 'i', 'n', 0 }; 65 | return s; 66 | } 67 | }; 68 | 69 | template<> struct period_name 70 | { 71 | template static const CharT* name() 72 | { 73 | static CharT s[] = { 'h', 0 }; 74 | return s; 75 | } 76 | }; 77 | } 78 | 79 | namespace std { namespace chrono { 80 | 81 | template 82 | typename std::enable_if< 83 | ::chrono_io_detail::native_period::value, 84 | std::basic_ostream& 85 | >::type operator<< (std::basic_ostream& os, const std::chrono::duration& t) 86 | { 87 | os << t.count() << ::chrono_io_detail::period_name::template name(); 88 | return os; 89 | } 90 | 91 | template 92 | typename std::enable_if< 93 | !::chrono_io_detail::native_period::value, 94 | std::basic_ostream& 95 | >::type operator<< (std::basic_ostream& os, const std::chrono::duration& t) 96 | { 97 | auto prev_prec = os.precision(); 98 | 99 | return os 100 | << std::setprecision(std::numeric_limits::digits10 + 1) 101 | << std::chrono::duration(t) 102 | << std::setprecision(prev_prec); 103 | } 104 | }} 105 | -------------------------------------------------------------------------------- /src/s11n.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include "ppconsul/config.h" 8 | #include "ppconsul/error.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | namespace ppconsul { namespace s11n { 18 | 19 | using json11::Json; 20 | 21 | namespace detail { 22 | inline Json parse_json(const std::string &s) 23 | { 24 | std::string err; 25 | auto obj = Json::parse(s, err); 26 | if (!err.empty()) 27 | throw FormatError(std::move(err)); 28 | return obj; 29 | } 30 | } 31 | 32 | inline std::string to_json(std::chrono::milliseconds ms) 33 | { 34 | return std::to_string(ms.count()) + "ms"; 35 | } 36 | 37 | inline std::string to_json(std::chrono::seconds s) 38 | { 39 | return std::to_string(s.count()) + "s"; 40 | } 41 | 42 | inline std::string to_json(std::chrono::minutes m) 43 | { 44 | return std::to_string(m.count()) + "m"; 45 | } 46 | 47 | inline void load(const Json& src, uint16_t& dst) 48 | { 49 | dst = static_cast(src.int_value()); 50 | } 51 | 52 | inline void load(const Json& src, bool& dst) 53 | { 54 | dst = static_cast(src.bool_value()); 55 | } 56 | 57 | inline void load(const Json& src, int& dst) 58 | { 59 | dst = static_cast(src.int_value()); 60 | } 61 | 62 | inline void load(const Json& src, uint64_t& dst) 63 | { 64 | // TODO: support full precision of uint64_t in json11 65 | dst = static_cast(src.number_value()); 66 | } 67 | 68 | inline void load(const Json& src, double& dst) 69 | { 70 | dst = src.number_value(); 71 | } 72 | 73 | inline void load(const Json& src, std::chrono::minutes& dst) 74 | { 75 | dst = std::chrono::minutes(static_cast(src.number_value())); 76 | } 77 | 78 | inline void load(const Json& src, std::string& dst) 79 | { 80 | dst = src.string_value(); 81 | } 82 | 83 | template 84 | void load(const Json& src, std::vector& dst) 85 | { 86 | const auto& arr = src.array_items(); 87 | 88 | dst.clear(); 89 | dst.reserve(arr.size()); 90 | 91 | for (const auto& i : arr) 92 | { 93 | T t; 94 | load(i, t); 95 | dst.push_back(std::move(t)); 96 | } 97 | } 98 | 99 | template 100 | void load(const Json& src, std::set& dst) 101 | { 102 | const auto& arr = src.array_items(); 103 | 104 | dst.clear(); 105 | 106 | for (const auto& i : arr) 107 | { 108 | T t; 109 | load(i, t); 110 | dst.insert(std::move(t)); 111 | } 112 | } 113 | 114 | template 115 | void load(const Json& src, std::map& dst) 116 | { 117 | const auto& obj = src.object_items(); 118 | 119 | dst.clear(); 120 | 121 | for (const auto& i : obj) 122 | { 123 | T t; 124 | load(i.second, t); 125 | dst.emplace(i.first, std::move(t)); 126 | } 127 | } 128 | 129 | template 130 | void load(const Json& src, T& dst, const char *name) 131 | { 132 | load(src[name], dst); 133 | } 134 | 135 | template 136 | T parseJson(const std::string& jsonStr) 137 | { 138 | using namespace s11n; 139 | 140 | auto obj = detail::parse_json(jsonStr); 141 | T t; 142 | load(obj, t); 143 | return t; 144 | } 145 | }} 146 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | Releases: 4 | - Curently developing version: 0.1 5 | - Next version: 0.2 6 | - ... 7 | 8 | 9 | ### General 10 | 11 | + Add error message provided in response body into an exception throw 12 | + run integration tests manually 13 | + Encode URL (don't encode special characters) 14 | + Consistency modes 15 | + Blocking queries (simple) 16 | + Don't expose json11 in headers 17 | + Add CMake 3 support 18 | + build library as shared (default) or static (choosen by option) 19 | - Suppress all warnings. Possibly enable -Werror 20 | - Add async API (cURL multi handle?) (postponed to 0.2) 21 | - More checks of received JSON data shape (?) 22 | - Simplify JSON interface (?) 23 | - Don't use regex for CURL headers (it creates dependency on Boost.Regex compiled library on gcc 4.8) 24 | - provide comparision operators (at least ==, !=) for all structs defined (postponed to 0.2) 25 | - Support 64bit integers in JSON (postponed to 0.2) 26 | - tests for consistency / default consistency (if possible) 27 | - pkg-config: add Libs.private and Requires 28 | 29 | ### HTTP(S) 30 | ? allow user to set CURLOPT_SSLENGINE and CURLOPT_SSLENGINE_DEFAULT (consul 0.2) 31 | ? allow user to set CURLOPT_SSL_CIPHER_LIST 32 | or set it by ourself to something highly secure (check what Consul supports) (consul 0.2) 33 | ? allow user to set CURLOPT_ISSUERCERT (consul 0.2) 34 | ? allow user to set CURLOPT_PINNEDPUBLICKEY (consul 0.2) 35 | ? allow user to set CURLOPT_CRLFILE (consul 0.2) 36 | ? allow user to set some other HTTP(S) options supported by cURL 37 | ? use Consul::setOption() instead of adding all of this to TlsOptions? 38 | 39 | ### CI 40 | 41 | + build for 3 platforms (Windows, Linux, OSX) 42 | + run unit-tests 43 | + run integration tests (+Windows, +Linux , +OSX) 44 | + operator << for std::duration for tests 45 | + split consul tests into different executables 46 | 47 | ### Consul 48 | + Change 'block-for' resolution from seconds to milliseconds 49 | - tests for default DC and default token (postponed to 0.2) 50 | 51 | 52 | ### kv 53 | 54 | + v1 interface except acquire/release lock and consistency modes 55 | + Decode value from base64 56 | + Integration tests 57 | + URL encode keys (key may contain any char from 0 to 255) 58 | + ensure value can contain any char from 0 to 255 59 | + Consistency modes 60 | + Blocking queries (simple) 61 | - Acquire/release lock (depends on session) 62 | - Get flag as uin64_t. Now it extracted from JSON as double because of json11 limitations - switch to RapidJSON 63 | 64 | - throwing `std::string get(key, params...)` i.e. w/o a default value 65 | - `boost::optional getOptional(key, params...)` 66 | - lazy items iterator 67 | - ? syntax sugar operator[] to simply get/put value w/o params. getter returns "" if key does not exist (postponed to 0.2?) 68 | - tests for dc (postponed to 0.2) 69 | - tests for default DC and default token (postponed to 0.2) 70 | - typed value access (postponed to 0.2) 71 | 72 | 73 | ### agent 74 | 75 | + v1 interface except Config part of /v1/agent/self 76 | + Additional check types (https://www.consul.io/docs/agent/checks.html) 77 | + HTTP 78 | + TCP 79 | + Docker 80 | + Support "Notes" for a service's check 81 | - Support multiple checks for a service 82 | - Support "Status" (plus "Output"?) for a check's initial status 83 | - agent/maintenance (https://www.consul.io/docs/agent/http/agent.html#agent_maintenance) 84 | - agent/service/maintenance (https://www.consul.io/docs/agent/http/agent.html#agent_service_maintenance) 85 | 86 | - Config part of /v1/agent/self (postponed to 0.2) 87 | 88 | 89 | ### catalog 90 | 91 | + v1 interface except register/deregister 92 | + tests 93 | + tests for block_for 94 | 95 | - tests for dc (postponed to 0.2) 96 | - tests for default DC (postponed to 0.2) 97 | - register/deregister (no reason to implement) 98 | 99 | 100 | ### health 101 | 102 | + v1 interface except register/deregister 103 | - tests 104 | - tests for block_for 105 | 106 | - tests for dc (postponed to 0.2) 107 | - tests for default DC (postponed to 0.2) 108 | 109 | 110 | ### session 111 | 112 | ### event 113 | 114 | ### status 115 | 116 | ### acl 117 | 118 | -------------------------------------------------------------------------------- /src/curl/http_client.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #pragma once 8 | 9 | #include 10 | #include "http_helpers.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | namespace ppconsul { namespace curl { 18 | 19 | namespace detail 20 | { 21 | struct CurlEasyDeleter 22 | { 23 | void operator() (CURL *handle) const noexcept 24 | { 25 | curl_easy_cleanup(handle); 26 | } 27 | }; 28 | 29 | struct CurlSListDeleter 30 | { 31 | void operator() (curl_slist * slist) const noexcept 32 | { 33 | curl_slist_free_all(slist); 34 | } 35 | }; 36 | } 37 | 38 | using CurlHeaderList = std::unique_ptr; 39 | 40 | struct CurlInitializer 41 | { 42 | CurlInitializer() 43 | { 44 | m_initialized = 0 == curl_global_init(CURL_GLOBAL_DEFAULT | CURL_GLOBAL_SSL); 45 | } 46 | 47 | ~CurlInitializer() 48 | { 49 | curl_global_cleanup(); 50 | } 51 | 52 | CurlInitializer(const CurlInitializer&) = delete; 53 | CurlInitializer& operator= (const CurlInitializer&) = delete; 54 | 55 | explicit operator bool() const { return m_initialized; } 56 | 57 | private: 58 | bool m_initialized; 59 | }; 60 | 61 | class CurlHttpClient: public ppconsul::http::HttpClient 62 | { 63 | public: 64 | CurlHttpClient(const std::string& endpoint, 65 | const ppconsul::http::HttpClientConfig& config, 66 | const std::function& cancellationCallback); 67 | 68 | virtual ~CurlHttpClient() override; 69 | 70 | GetResponse get(const std::string& path, const std::string& query, 71 | const http::RequestHeaders & headers = http::RequestHeaders{}) override; 72 | PutResponse put(const std::string& path, const std::string& query, const std::string& data, 73 | const http::RequestHeaders & headers = http::RequestHeaders{}) override; 74 | DelResponse del(const std::string& path, const std::string& query, 75 | const http::RequestHeaders & headers = http::RequestHeaders{}) override; 76 | 77 | bool stopped() const noexcept { return m_cancellationCallback(); } 78 | 79 | CurlHttpClient(const CurlHttpClient&) = delete; 80 | CurlHttpClient(CurlHttpClient&&) = delete; 81 | CurlHttpClient& operator= (const CurlHttpClient&) = delete; 82 | CurlHttpClient& operator= (CurlHttpClient&&) = delete; 83 | 84 | private: 85 | void setupTls(const ppconsul::http::TlsConfig& tlsConfig); 86 | 87 | template 88 | void setopt(Opt opt, const T& t); 89 | 90 | std::string makeUrl(const std::string& path, const std::string& query) const { return ppconsul::http::impl::makeUrl(m_endpoint, path, query); } 91 | 92 | CURL *handle() const noexcept { return m_handle.get(); } 93 | 94 | void perform(); 95 | 96 | void setHeaders(const http::RequestHeaders & headers); 97 | 98 | std::function m_cancellationCallback; 99 | 100 | std::string m_endpoint; 101 | std::unique_ptr m_handle; 102 | CurlHeaderList m_headers; 103 | char m_errBuffer[CURL_ERROR_SIZE]; // Replace with unique_ptr> if moving is needed 104 | }; 105 | 106 | struct CurlHttpClientFactory 107 | { 108 | std::unique_ptr operator() (const std::string& endpoint, 109 | const ppconsul::http::HttpClientConfig& config, 110 | std::function cancellationCallback) const; 111 | }; 112 | }} 113 | -------------------------------------------------------------------------------- /src/consul.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include "ppconsul/consul.h" 8 | #include "ppconsul/helpers.h" 9 | #include "curl/http_client.h" 10 | 11 | 12 | namespace ppconsul { 13 | 14 | const char *BadStatus::what() const noexcept 15 | { 16 | if (m_what.empty()) 17 | { 18 | if (!m_message.empty()) 19 | { 20 | m_what = helpers::format("%s [%03d %s]", 21 | m_message.c_str(), 22 | m_status.code(), 23 | m_status.message().c_str()); 24 | } 25 | else 26 | { 27 | m_what = helpers::format("%03d %s", 28 | m_status.code(), 29 | m_status.message().c_str()); 30 | } 31 | } 32 | 33 | return m_what.c_str(); 34 | } 35 | 36 | HttpClientFactory makeDefaultHttpClientFactory(bool initCurl) 37 | { 38 | if (initCurl) { 39 | static const curl::CurlInitializer g_initialized; 40 | 41 | if (!g_initialized) 42 | throw std::runtime_error("CURL was not successfully initialized"); 43 | } 44 | 45 | return curl::CurlHttpClientFactory{}; 46 | } 47 | 48 | struct Consul::Impl 49 | { 50 | Impl(HttpClientFactory clientFactory, std::string dataCenter, std::string defaultToken, std::string endpoint, ppconsul::http::HttpClientConfig clientConfig, bool enableStop) 51 | : m_clientFactory(std::move(clientFactory)) 52 | , m_dataCenter(std::move(dataCenter)) 53 | , m_defaultToken(std::move(defaultToken)) 54 | , m_endpoint(std::move(endpoint)) 55 | , m_clientConfig(std::move(clientConfig)) 56 | , m_enableStop(enableStop) 57 | , m_stopped{false} 58 | , m_clientPool([this](){ return createClient(); }) 59 | {} 60 | 61 | std::unique_ptr createClient() const 62 | { 63 | CancellationCallback cancellationCallback; 64 | if (m_enableStop) 65 | cancellationCallback = [this](){ return stopped(); }; 66 | 67 | return m_clientFactory(m_endpoint, m_clientConfig, std::move(cancellationCallback)); 68 | } 69 | 70 | bool stopped() const noexcept 71 | { 72 | return m_stopped.load(std::memory_order_relaxed); 73 | } 74 | 75 | void stop() 76 | { 77 | if (!m_enableStop) 78 | throw std::logic_error("Must enable stop at construction time"); 79 | m_stopped.store(true, std::memory_order_relaxed); 80 | } 81 | 82 | HttpClientFactory m_clientFactory; 83 | std::string m_dataCenter; 84 | std::string m_defaultToken; 85 | std::string m_endpoint; 86 | ppconsul::http::HttpClientConfig m_clientConfig; 87 | bool m_enableStop; 88 | std::atomic_bool m_stopped; 89 | ClientPool m_clientPool; 90 | }; 91 | 92 | Consul::Consul(HttpClientFactory clientFactory, std::string defaultToken, std::string dataCenter, std::string endpoint, 93 | http::HttpClientConfig clientConfig, bool enableStop) 94 | : m_impl(new Impl{std::move(clientFactory), std::move(dataCenter), std::move(defaultToken), 95 | helpers::ensureScheme(endpoint), std::move(clientConfig), enableStop}) 96 | {} 97 | 98 | Consul::~Consul() = default; 99 | 100 | Consul::Consul(Consul &&op) noexcept = default; 101 | Consul& Consul::operator= (Consul &&op) noexcept = default; 102 | 103 | ClientPool::ClientPtr Consul::getClient() const 104 | { 105 | return m_impl->m_clientPool(); 106 | } 107 | 108 | const std::string& Consul::dataCenter() const noexcept 109 | { 110 | return m_impl->m_dataCenter; 111 | } 112 | 113 | const std::string& Consul::defaultToken() const noexcept 114 | { 115 | return m_impl->m_defaultToken; 116 | } 117 | 118 | bool Consul::stopped() const noexcept 119 | { 120 | return m_impl->stopped(); 121 | } 122 | 123 | void Consul::stop() 124 | { 125 | m_impl->stop(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-2020 Andrey Upadyshev 2 | # 3 | # Use, modification and distribution are subject to the 4 | # Boost Software License, Version 1.0. (See accompanying file 5 | # LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | cmake_minimum_required(VERSION 3.1) 8 | 9 | find_package(Git) 10 | if(GIT_FOUND) 11 | execute_process( 12 | COMMAND ${GIT_EXECUTABLE} describe --match "v[0-9]*" --abbrev=0 --tags HEAD 13 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 14 | OUTPUT_VARIABLE DESCRIBE_BUILD 15 | OUTPUT_STRIP_TRAILING_WHITESPACE 16 | ) 17 | string(REGEX REPLACE "^v" "" VERSION ${DESCRIBE_BUILD}) 18 | else() 19 | set(VERSION "0.0.0") 20 | endif() 21 | 22 | message(STATUS "Building version: ${VERSION}") 23 | 24 | project(Ppconsul VERSION ${VERSION}) 25 | 26 | include(./conan_paths.cmake OPTIONAL) 27 | 28 | if (WIN32) 29 | option(BUILD_STATIC_LIB "Build Ppconsul as static library" ON) 30 | else() 31 | option(BUILD_STATIC_LIB "Build Ppconsul as static library" OFF) 32 | endif() 33 | 34 | include(GNUInstallDirs) 35 | 36 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output) 37 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output) 38 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output) 39 | 40 | set(BOOST_MIN_VERSION 1.55) # Because of https://svn.boost.org/trac/boost/ticket/8759 41 | set(USE_BOOST_REGEX OFF) 42 | 43 | if (${CMAKE_CXX_COMPILER_ID} MATCHES GNU) 44 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC") 45 | 46 | if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) 47 | set(USE_BOOST_REGEX ON) 48 | message(STATUS "GCC ${GCC_VERSION} found. Note that using of GCC version less then 4.9 requires to link with Boost.Regex library") 49 | endif() 50 | elseif (${CMAKE_CXX_COMPILER_ID} MATCHES Clang) 51 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -ftemplate-depth=256") 52 | elseif (MSVC) 53 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") 54 | 55 | # Turn off MS specific warnings that shown for standard compatible code (mostly for Boost) 56 | add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS) 57 | 58 | # Build for Windows Vista / Windows Server 2008 59 | add_definitions(-D_WIN32_WINNT=0x0600) 60 | 61 | set(Boost_USE_STATIC_LIBS ON) 62 | #set(Boost_USE_STATIC_RUNTIME OFF) 63 | endif() 64 | 65 | 66 | if (NOT ${USE_BOOST_REGEX}) 67 | find_package(Boost ${BOOST_MIN_VERSION} REQUIRED) 68 | else () 69 | find_package(Boost ${BOOST_MIN_VERSION} REQUIRED COMPONENTS regex) 70 | add_definitions(-DPPCONSUL_USE_BOOST_REGEX) 71 | endif () 72 | 73 | # Add user specified path to CURL headers/libraries into CMAKE_INCLUDE_PATH/CMAKE_LIBRARY_PATH variables. 74 | # Otherwise CURL could not be found on Windows 75 | if ("${CURL_ROOT}" STREQUAL "") 76 | set (CURL_ROOT "$ENV{CURL_ROOT}") 77 | endif () 78 | 79 | if (NOT ${CURL_ROOT} STREQUAL "") 80 | set (CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} "${CURL_ROOT}/include") 81 | set (CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} "${CURL_ROOT}/lib") 82 | endif () 83 | find_package(CURL REQUIRED) 84 | 85 | set(LIBB64_DIR "${PROJECT_SOURCE_DIR}/ext/b64") 86 | set(LIBB64_SOURCES "${LIBB64_DIR}/cdecode.h" "${LIBB64_DIR}/cdecode.c" "${LIBB64_DIR}/cencode.h" "${LIBB64_DIR}/cencode.c") 87 | 88 | set(CATCH_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}/ext/catch") 89 | set(HEADERS_DIR "${PROJECT_SOURCE_DIR}/include/ppconsul") 90 | 91 | if (WIN32 AND NOT BUILD_STATIC_LIB) 92 | message(FATAL_ERROR "Building Ppconsul as dynamic library on Windows is not supported, see https://github.com/oliora/ppconsul/issues/25") 93 | endif() 94 | 95 | add_subdirectory(ext/json11) 96 | add_subdirectory(src) 97 | 98 | option(BUILD_TESTS "Flag to use to build test or not" ON) 99 | if(BUILD_TESTS) 100 | enable_testing() 101 | add_subdirectory(tests) 102 | endif() 103 | 104 | install( 105 | DIRECTORY "${HEADERS_DIR}" 106 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 107 | ) 108 | 109 | install(EXPORT ${CMAKE_PROJECT_NAME} DESTINATION cmake FILE ppconsulConfig.cmake) 110 | export(EXPORT ${CMAKE_PROJECT_NAME} FILE ppconsulConfig.cmake) 111 | 112 | # Generate and install pkg-config file 113 | if (NOT WIN32 OR CYGWIN) 114 | if (BUILD_STATIC_LIB) 115 | set(ppconsul_libs "-lppconsul -ljson11") 116 | else() 117 | set(ppconsul_libs "-lppconsul") 118 | endif() 119 | 120 | configure_file(ppconsul.pc.in ppconsul.pc @ONLY) 121 | 122 | install( 123 | FILES "${CMAKE_CURRENT_BINARY_DIR}/ppconsul.pc" 124 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" 125 | ) 126 | endif() 127 | 128 | -------------------------------------------------------------------------------- /tests/coordinate/coordinate_tests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | 9 | #include "ppconsul/coordinate.h" 10 | #include "ppconsul/agent.h" 11 | #include "test_consul.h" 12 | 13 | #include 14 | 15 | using namespace ppconsul::coordinate; 16 | using ppconsul::agent::Agent; 17 | 18 | namespace { 19 | const auto Non_Existing_Node_Name = "D0087276-8F85-4612-AC88-8871DB15B2A7"; 20 | 21 | // Verifies that all nodes have the same dimensionality 22 | bool sameDim(const std::vector& nodes) 23 | { 24 | if (nodes.empty()) 25 | { 26 | return true; 27 | } 28 | 29 | auto dim = nodes.front().coord.vec.size(); 30 | 31 | return std::all_of( 32 | nodes.begin(), nodes.end(), 33 | [dim](const Node& node) 34 | { 35 | return (node.coord.vec.size() == dim); 36 | } 37 | ); 38 | } 39 | 40 | } 41 | 42 | TEST_CASE("coordinate.datacenters", "[consul][coordinate]") 43 | { 44 | auto consul = create_test_consul(); 45 | Coordinate coordinate(consul); 46 | 47 | auto dcs = coordinate.datacenters(); 48 | 49 | REQUIRE_FALSE(dcs.empty()); 50 | auto it = std::find_if(dcs.begin(), dcs.end(), [&](const Datacenter& op){ 51 | return op.datacenter== get_test_datacenter(); 52 | }); 53 | CHECK(it != dcs.end()); 54 | 55 | for (const auto& d : dcs) 56 | { 57 | CHECK_FALSE(d.datacenter.empty()); 58 | CHECK_FALSE(d.areaId.empty()); 59 | 60 | REQUIRE_FALSE(d.coordinates.empty()); 61 | for (const auto& node : d.coordinates) 62 | { 63 | CHECK_FALSE(node.node.empty()); 64 | CHECK_FALSE(node.coord.vec.empty()); 65 | } 66 | CHECK(sameDim(d.coordinates)); 67 | } 68 | } 69 | 70 | TEST_CASE("coordinate.nodes", "[consul][coordinate]") 71 | { 72 | auto consul = create_test_consul(); 73 | Coordinate coordinate(consul); 74 | 75 | auto self = Agent(consul).self(); 76 | 77 | auto nodes = coordinate.nodes(); 78 | 79 | REQUIRE_FALSE(nodes.empty()); 80 | 81 | auto it = std::find_if(nodes.begin(), nodes.end(), [&](const Node& op){ 82 | return op.node == self.member.name; 83 | }); 84 | REQUIRE(it != nodes.end()); 85 | 86 | for (const auto& node : nodes) 87 | { 88 | CHECK_FALSE(node.node.empty()); 89 | CHECK_FALSE(node.coord.vec.empty()); 90 | } 91 | CHECK(sameDim(nodes)); 92 | } 93 | 94 | TEST_CASE("coordinate.node", "[consul][coordinate]") 95 | { 96 | auto consul = create_test_consul(); 97 | Coordinate coordinate(consul); 98 | 99 | auto self = Agent(consul).self(); 100 | 101 | auto node = coordinate.node(self.member.name); 102 | 103 | REQUIRE(node.size() >= 1); 104 | for (const auto& nodeRec : node) 105 | { 106 | CHECK(nodeRec.node == self.member.name); 107 | CHECK_FALSE(nodeRec.coord.vec.empty()); 108 | } 109 | CHECK(sameDim(node)); 110 | 111 | CHECK(coordinate.node(Non_Existing_Node_Name).empty()); 112 | } 113 | 114 | TEST_CASE("coordinate.node_float_parse", "[consul][coordinate][parse]") 115 | { 116 | const std::string json("{" 117 | "\"Node\": \"core-1\"," 118 | "\"Segment\": \"segment\"," 119 | "\"Coord\": {" 120 | "\"Vec\": [" 121 | "-0.0030908182083252632," 122 | "-0.0034924296469496137," 123 | "0.004027661974926579," 124 | "0.009441736044369261," 125 | "0.009120584954036386," 126 | "0.005945711502389089," 127 | "-0.005375781324440643," 128 | "0.002691224249211683" 129 | "]," 130 | "\"Error\": 0.33682855812450196," 131 | "\"Adjustment\": -8.957602083707684e-05," 132 | "\"Height\": 0.0005342546178363255" 133 | "}" 134 | "}"); 135 | 136 | auto node = impl::parseNode(json); 137 | 138 | CHECK(node.node == "core-1"); 139 | CHECK(node.segment == "segment"); 140 | 141 | REQUIRE(node.coord.vec.size() == 8); 142 | CHECK(node.coord.vec[0] == Approx(-0.0030908182083252632)); 143 | CHECK(node.coord.vec[1] == Approx(-0.0034924296469496137)); 144 | CHECK(node.coord.vec[2] == Approx(0.004027661974926579)); 145 | CHECK(node.coord.vec[3] == Approx(0.009441736044369261)); 146 | CHECK(node.coord.vec[4] == Approx(0.009120584954036386)); 147 | CHECK(node.coord.vec[5] == Approx(0.005945711502389089)); 148 | CHECK(node.coord.vec[6] == Approx(-0.005375781324440643)); 149 | CHECK(node.coord.vec[7] == Approx(0.002691224249211683)); 150 | 151 | CHECK(node.coord.error == Approx(0.33682855812450196)); 152 | CHECK(node.coord.adjustment == Approx(-0.00008957602083707684)); 153 | CHECK(node.coord.height == Approx(0.0005342546178363255)); 154 | } 155 | -------------------------------------------------------------------------------- /include/ppconsul/coordinate.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | # pragma once 8 | 9 | #include "ppconsul/consul.h" 10 | #include "ppconsul/helpers.h" 11 | #include "ppconsul/types.h" 12 | 13 | namespace ppconsul { namespace coordinate { 14 | 15 | using Value = ppconsul::Coordinate; 16 | 17 | struct Node 18 | { 19 | std::string node; 20 | std::string segment; 21 | Value coord; 22 | }; 23 | 24 | struct Datacenter 25 | { 26 | std::string datacenter; 27 | std::string areaId; 28 | std::vector coordinates; 29 | }; 30 | 31 | namespace kw { 32 | using ppconsul::kw::consistency; 33 | using ppconsul::kw::block_for; 34 | using ppconsul::kw::dc; 35 | 36 | KWARGS_KEYWORD(segment, std::string) 37 | 38 | namespace groups { 39 | KWARGS_KEYWORDS_GROUP(get, (consistency, dc, block_for)) 40 | } 41 | } 42 | 43 | namespace impl { 44 | std::vector parseDatacenters(const std::string& json); 45 | std::vector parseNodes(const std::string& json); 46 | Node parseNode(const std::string& json); 47 | } 48 | 49 | class Coordinate 50 | { 51 | public: 52 | template> 53 | explicit Coordinate(Consul& consul, const Params&... params) 54 | : m_consul(consul) 55 | , m_defaultConsistency(kwargs::get_opt(kw::consistency, Consistency::Default, params...)) 56 | , m_defaultDc(kwargs::get_opt(kw::dc, std::string(), params...)) 57 | { 58 | KWARGS_CHECK_IN_LIST(Params, (kw::consistency, kw::dc)) 59 | } 60 | 61 | std::vector datacenters() const 62 | { 63 | return impl::parseDatacenters(m_consul.get("/v1/coordinate/datacenters")); 64 | } 65 | 66 | // Result contains both headers and data. 67 | // Allowed parameters: 68 | // - segment 69 | // - group::get 70 | template> 71 | Response> nodes(WithHeaders, const Params&... params) const; 72 | 73 | // Result contains data only. 74 | // Allowed parameters: 75 | // - segment 76 | // - group::get 77 | template> 78 | std::vector nodes(const Params&... params) const 79 | { 80 | return std::move(nodes(withHeaders, params...).data()); 81 | } 82 | 83 | // Returns empty vector if node does not exist 84 | // Result contains both headers and data. 85 | // Allowed parameters: 86 | // - group::get 87 | template> 88 | Response> node(WithHeaders, const std::string& name, const Params&... params) const; 89 | 90 | // Returns empty vector if node does not exist 91 | // Result contains data only. 92 | // Allowed parameters: 93 | // - group::get 94 | template> 95 | std::vector node(const std::string& name, const Params&... params) const 96 | { 97 | return std::move(node(withHeaders, name, params...).data()); 98 | } 99 | 100 | private: 101 | Consul& m_consul; 102 | 103 | Consistency m_defaultConsistency; 104 | std::string m_defaultDc; 105 | }; 106 | 107 | // Implementation 108 | 109 | template 110 | Response> Coordinate::nodes(WithHeaders, const Params&... params) const 111 | { 112 | KWARGS_CHECK_IN_LIST(Params, (kw::groups::get, kw::segment)); 113 | auto r = m_consul.get(withHeaders, "/v1/coordinate/nodes", 114 | kw::consistency = m_defaultConsistency, kw::dc = m_defaultDc, 115 | params...); 116 | return makeResponse(r.headers(), impl::parseNodes(r.data())); 117 | } 118 | 119 | template 120 | Response> Coordinate::node(WithHeaders, const std::string& name, const Params&... params) const 121 | { 122 | KWARGS_CHECK_IN_LIST(Params, (kw::groups::get)); 123 | http::Status s; 124 | auto r = m_consul.get(s, "/v1/coordinate/node/" + helpers::encodeUrl(name), 125 | kw::consistency = m_defaultConsistency, kw::dc = m_defaultDc, 126 | params...); 127 | if (s.success()) 128 | return makeResponse(r.headers(), std::move(impl::parseNodes(r.data()))); 129 | if (NotFoundError::Code == s.code()) 130 | return{ r.headers() }; 131 | throw BadStatus(std::move(s), std::move(r.data())); 132 | } 133 | }} 134 | -------------------------------------------------------------------------------- /include/ppconsul/sessions.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #pragma once 8 | 9 | #include "ppconsul/consul.h" 10 | #include "ppconsul/helpers.h" 11 | #include "ppconsul/types.h" 12 | #include 13 | 14 | namespace ppconsul { namespace sessions { 15 | 16 | enum class InvalidationBehavior 17 | { 18 | Release, 19 | Delete, 20 | }; 21 | 22 | namespace kw { 23 | using ppconsul::kw::dc; 24 | using ppconsul::kw::token; 25 | 26 | KWARGS_KEYWORD(name, std::string) 27 | KWARGS_KEYWORD(node, std::string) 28 | KWARGS_KEYWORD(lock_delay, std::chrono::seconds) 29 | KWARGS_KEYWORD(behavior, InvalidationBehavior) 30 | KWARGS_KEYWORD(ttl, std::chrono::seconds) 31 | 32 | namespace groups { 33 | KWARGS_KEYWORDS_GROUP(put, (dc, token)) 34 | } 35 | } 36 | 37 | namespace impl { 38 | std::string createBodyJson(const std::string &name, 39 | const std::string &node, 40 | std::chrono::seconds lockDelay, 41 | InvalidationBehavior behavior, 42 | std::chrono::seconds ttl); 43 | 44 | std::string parseCreateResponse(const std::string &resp); 45 | } 46 | 47 | class Sessions 48 | { 49 | public: 50 | // Allowed parameters: 51 | // - token - default token for all requests 52 | // - dc - default dc for all requests 53 | template> 54 | explicit Sessions(Consul& consul, const Params&... params) 55 | : m_consul(consul) 56 | , m_defaultToken(kwargs::get_opt(kw::token, std::string(), params...)) 57 | , m_defaultDc(kwargs::get_opt(kw::dc, std::string(), params...)) 58 | { 59 | KWARGS_CHECK_IN_LIST(Params, (kw::token, kw::dc)) 60 | } 61 | 62 | // Create a new session. Returns the ID of the created session. 63 | // Allowed parameters: 64 | // - name - a human-readable name for the session 65 | // - node - the name of the node (current agent by default) 66 | // - lock_delay - the duration for the lock delay (15s by default) 67 | // - behavior - the behavior to take when the session is invalidated (release by default) 68 | // - ttl - session TTL 69 | // - groups::put 70 | template> 71 | std::string create(const Params&... params) const; 72 | 73 | // Renew an existing session by its ID. Note that this operation only makes sense if the session has a TTL. 74 | // Allowed parameters: 75 | // - groups::put 76 | template> 77 | void renew(const std::string &session, const Params&... params) const 78 | { 79 | KWARGS_CHECK_IN_LIST(Params, (kw::groups::put)) 80 | 81 | m_consul.put( 82 | "/v1/session/renew/" + helpers::encodeUrl(session), "", 83 | kw::token = m_defaultToken, kw::dc = m_defaultDc, params...); 84 | } 85 | 86 | // Destroy a session by its ID. Returns true on success and false on failure. 87 | // Allowed parameters: 88 | // - groups::put 89 | template> 90 | bool destroy(const std::string &session, const Params&... params) const 91 | { 92 | KWARGS_CHECK_IN_LIST(Params, (kw::groups::put)) 93 | 94 | return helpers::parseJsonBool(m_consul.put( 95 | "/v1/session/destroy/" + helpers::encodeUrl(session), "", 96 | kw::token = m_defaultToken, kw::dc = m_defaultDc, params...)); 97 | } 98 | 99 | private: 100 | Consul& m_consul; 101 | 102 | std::string m_defaultToken; 103 | std::string m_defaultDc; 104 | }; 105 | 106 | 107 | // Implementation 108 | 109 | template 110 | std::string Sessions::create(const Params&... params) const 111 | { 112 | KWARGS_CHECK_IN_LIST(Params, (kw::groups::put, kw::name, kw::node, kw::lock_delay, kw::behavior, kw::ttl)) 113 | 114 | auto resp = m_consul.put( 115 | "/v1/session/create", 116 | impl::createBodyJson( 117 | kwargs::get_opt(kw::name, std::string{}, params...), 118 | kwargs::get_opt(kw::node, std::string{}, params...), 119 | kwargs::get_opt(kw::lock_delay, std::chrono::seconds{-1}, params...), 120 | kwargs::get_opt(kw::behavior, InvalidationBehavior::Release, params...), 121 | kwargs::get_opt(kw::ttl, std::chrono::seconds{-1}, params...) 122 | ), 123 | kw::token = kwargs::get_opt(kw::token, m_defaultToken, params...), // TODO: implement keywords filtering 124 | kw::dc = kwargs::get_opt(kw::dc, m_defaultDc, params...) // 125 | ); 126 | 127 | return impl::parseCreateResponse(resp); 128 | } 129 | }} 130 | -------------------------------------------------------------------------------- /tests/agent/agent_other_tests.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | 9 | #include "ppconsul/agent.h" 10 | #include "test_consul.h" 11 | #include 12 | #include 13 | 14 | 15 | using namespace ppconsul::agent; 16 | using ppconsul::CheckStatus; 17 | 18 | 19 | /*namespace { 20 | auto const Non_Existing_Script_Name = "63E7A7B1-FDAC-4D49-9F8F-1479C866815D"; 21 | auto const Unique_Id = "{16CA1AC9-72EE-451D-970E-E520B4EF874A}"; 22 | auto const Non_Existing_Check_Name = "DE2F4D40-2664-472D-B0B7-EA0A47D92136"; 23 | }*/ 24 | 25 | TEST_CASE("agent.self", "[consul][agent][config][self]") 26 | { 27 | auto consul = create_test_consul(); 28 | Agent agent(consul); 29 | 30 | auto self = agent.self(); 31 | CHECK(!self.config.datacenter.empty()); 32 | CHECK(!self.config.nodeName.empty()); 33 | CHECK(!self.config.nodeId.empty()); 34 | CHECK(!self.config.version.empty()); 35 | // seems like self.config.revision can be empty 36 | 37 | CHECK(!self.member.name.empty()); 38 | CHECK(!self.member.address.empty()); 39 | CHECK(self.member.port); 40 | CHECK(!self.member.tags.empty()); 41 | CHECK(self.member.status); 42 | CHECK(self.member.protocolMin); 43 | CHECK(self.member.protocolMin); 44 | CHECK(self.member.protocolMax); 45 | CHECK(self.member.protocolCur); 46 | CHECK(self.member.delegateMin); 47 | CHECK(self.member.delegateMax); 48 | CHECK(self.member.delegateCur); 49 | 50 | CHECK(self.config.nodeName == self.member.name); 51 | } 52 | 53 | TEST_CASE("agent.members", "[consul][agent][config][members]") 54 | { 55 | using ppconsul::agent::Pool; 56 | 57 | auto consul = create_test_consul(); 58 | Agent agent(consul); 59 | 60 | SECTION("wan") 61 | { 62 | const auto members = agent.members(Pool::Wan); 63 | auto self = agent.self(); 64 | 65 | auto it1 = std::find_if(members.begin(), members.end(), [&](const ppconsul::agent::Member& op){ 66 | return op.address == self.member.address; 67 | }); 68 | 69 | REQUIRE(it1 != members.end()); 70 | 71 | const auto& m = *it1; 72 | 73 | CHECK(m.name.find(self.member.name + ".") == 0); 74 | CHECK(self.member.address == m.address); 75 | CHECK(m.port); 76 | 77 | // As recently discovered, self have extra tags thus filter them out first 78 | ppconsul::Metadata filteredTags; 79 | for (auto& tag: self.member.tags) 80 | if (m.tags.count(tag.first)) 81 | filteredTags.emplace(tag); 82 | CHECK(!m.tags.empty()); 83 | CHECK(filteredTags == m.tags); 84 | 85 | CHECK(self.member.status == m.status); 86 | CHECK(self.member.protocolMin == m.protocolMin); 87 | CHECK(self.member.protocolMin == m.protocolMin); 88 | CHECK(self.member.protocolMax == m.protocolMax); 89 | CHECK(self.member.protocolCur == m.protocolCur); 90 | CHECK(self.member.delegateMin == m.delegateMin); 91 | CHECK(self.member.delegateMax == m.delegateMax); 92 | CHECK(self.member.delegateCur == m.delegateCur); 93 | 94 | for (const auto& m : members) 95 | { 96 | CHECK(m.name != ""); 97 | CHECK(m.address != ""); 98 | CHECK(m.port != 0); 99 | CHECK(!m.tags.empty()); 100 | CHECK(m.status != 0); 101 | CHECK(m.protocolMin != 0); 102 | CHECK(m.protocolMax != 0); 103 | CHECK(m.protocolCur != 0); 104 | CHECK(m.delegateMin != 0); 105 | CHECK(m.delegateMax != 0); 106 | CHECK(m.delegateCur != 0); 107 | } 108 | } 109 | 110 | SECTION("lan") 111 | { 112 | const auto members = agent.members(); 113 | auto self = agent.self(); 114 | 115 | auto it1 = std::find_if(members.begin(), members.end(), [&](const ppconsul::agent::Member& op){ 116 | return op.address == self.member.address; 117 | }); 118 | 119 | REQUIRE(it1 != members.end()); 120 | 121 | const auto& m = *it1; 122 | 123 | CHECK((m.name == self.member.name 124 | || m.name.find(self.member.name + ".") == 0)); 125 | CHECK(self.member.address == m.address); 126 | CHECK(m.port); 127 | CHECK(self.member.tags == m.tags); 128 | CHECK(self.member.status == m.status); 129 | CHECK(self.member.protocolMin == m.protocolMin); 130 | CHECK(self.member.protocolMin == m.protocolMin); 131 | CHECK(self.member.protocolMax == m.protocolMax); 132 | CHECK(self.member.protocolCur == m.protocolCur); 133 | CHECK(self.member.delegateMin == m.delegateMin); 134 | CHECK(self.member.delegateMax == m.delegateMax); 135 | CHECK(self.member.delegateCur == m.delegateCur); 136 | 137 | // Useful only if the wan farm present 138 | CHECK(agent.members().size() == agent.members(Pool::Lan).size()); 139 | 140 | for (const auto& m : members) 141 | { 142 | CHECK(m.name != ""); 143 | CHECK(m.address != ""); 144 | CHECK(m.port != 0); 145 | CHECK(!m.tags.empty()); 146 | CHECK(m.status != 0); 147 | CHECK(m.protocolMin != 0); 148 | CHECK(m.protocolMax != 0); 149 | CHECK(m.protocolCur != 0); 150 | CHECK(m.delegateMin != 0); 151 | CHECK(m.delegateMax != 0); 152 | CHECK(m.delegateCur != 0); 153 | } 154 | } 155 | } 156 | 157 | TEST_CASE("agent.join_and_leave", "[consul][agent][config][join][leave]") 158 | { 159 | using ppconsul::agent::Pool; 160 | 161 | auto consul = create_test_consul(); 162 | Agent agent(consul); 163 | 164 | CHECK_NOTHROW(agent.forceLeave(agent.self().member.name)); 165 | CHECK_THROWS_AS(agent.join("127.0.0.1:21"), ppconsul::Error); 166 | CHECK_THROWS_AS(agent.join("127.0.0.1:21", Pool::Wan), ppconsul::Error); 167 | CHECK_THROWS_AS(agent.join("127.0.0.1:21", Pool::Lan), ppconsul::Error); 168 | CHECK_NOTHROW(agent.join("127.0.0.1")); 169 | CHECK_NOTHROW(agent.join("127.0.0.1", Pool::Wan)); 170 | CHECK_NOTHROW(agent.join("127.0.0.1", Pool::Lan)); 171 | } 172 | -------------------------------------------------------------------------------- /src/agent.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | 8 | #include "ppconsul/agent.h" 9 | #include "s11n_types.h" 10 | 11 | namespace ppconsul { namespace agent { 12 | using s11n::load; 13 | 14 | inline void load(const json11::Json& src, SelfInfo& dst) 15 | { 16 | load(src, dst.config, "Config"); 17 | load(src, dst.member, "Member"); 18 | load(src, dst.coord, "Coord"); 19 | } 20 | 21 | void load(const s11n::Json& src, Config& dst) 22 | { 23 | load(src, dst.datacenter, "Datacenter"); 24 | load(src, dst.nodeName, "NodeName"); 25 | load(src, dst.nodeId, "NodeID"); 26 | load(src, dst.server, "Server"); 27 | load(src, dst.revision, "Revision"); 28 | load(src, dst.version, "Version"); 29 | } 30 | 31 | void load(const s11n::Json& src, Member& dst) 32 | { 33 | load(src, dst.name, "Name"); 34 | load(src, dst.address, "Addr"); 35 | load(src, dst.port, "Port"); 36 | load(src, dst.tags, "Tags"); 37 | load(src, dst.status, "Status"); 38 | load(src, dst.protocolMin, "ProtocolMin"); 39 | load(src, dst.protocolMax, "ProtocolMax"); 40 | load(src, dst.protocolCur, "ProtocolCur"); 41 | load(src, dst.delegateMin, "DelegateMin"); 42 | load(src, dst.delegateMax, "DelegateMax"); 43 | load(src, dst.delegateCur, "DelegateCur"); 44 | } 45 | 46 | 47 | namespace impl { 48 | 49 | namespace { 50 | using s11n::to_json; 51 | 52 | s11n::Json::array to_json(const StringList& strings) 53 | { 54 | return s11n::Json::array(strings.begin(), strings.end()); 55 | } 56 | 57 | s11n::Json::array to_json(const Tags& tags) 58 | { 59 | return s11n::Json::array(tags.begin(), tags.end()); 60 | } 61 | 62 | s11n::Json::object to_json(const Metadata& meta) 63 | { 64 | return s11n::Json::object(meta.begin(), meta.end()); 65 | } 66 | 67 | struct CheckConfigSaver: boost::static_visitor<> 68 | { 69 | CheckConfigSaver(s11n::Json::object& dst): dst_(&dst) {} 70 | 71 | void operator() (const TtlCheck& c) const 72 | { 73 | dst()["ttl"] = to_json(c.ttl); 74 | } 75 | 76 | void operator() (const ScriptCheck& c) const 77 | { 78 | dst()["script"] = c.script; 79 | dst()["interval"] = to_json(c.interval); 80 | } 81 | 82 | void operator() (const CommandCheck& c) const 83 | { 84 | dst()["args"] = to_json(c.args); 85 | dst()["interval"] = to_json(c.interval); 86 | } 87 | 88 | void operator() (const HttpCheck& c) const 89 | { 90 | dst()["http"] = c.url; 91 | dst()["interval"] = to_json(c.interval); 92 | if (c.timeout != decltype(c.timeout)::zero()) 93 | dst()["timeout"] = to_json(c.timeout); 94 | } 95 | 96 | void operator() (const TcpCheck& c) const 97 | { 98 | dst()["tcp"] = c.address; 99 | dst()["interval"] = to_json(c.interval); 100 | if (c.timeout != decltype(c.timeout)::zero()) 101 | dst()["timeout"] = to_json(c.timeout); 102 | } 103 | 104 | void operator() (const DockerScriptCheck& c) const 105 | { 106 | dst()["docker_container_id"] = c.containerId; 107 | dst()["shell"] = c.shell; 108 | dst()["script"] = c.script; 109 | dst()["interval"] = to_json(c.interval); 110 | } 111 | 112 | void operator() (const DockerCommandCheck& c) const 113 | { 114 | dst()["docker_container_id"] = c.containerId; 115 | dst()["shell"] = c.shell; 116 | dst()["args"] = to_json(c.args); 117 | dst()["interval"] = to_json(c.interval); 118 | } 119 | 120 | s11n::Json::object& dst() const { return *dst_; } 121 | 122 | private: 123 | s11n::Json::object* dst_; 124 | }; 125 | 126 | 127 | void save(s11n::Json::object& dst, const CheckParams& c) 128 | { 129 | boost::apply_visitor(CheckConfigSaver(dst), c); 130 | } 131 | } 132 | 133 | std::vector parseMembers(const std::string& json) 134 | { 135 | return s11n::parseJson>(json); 136 | } 137 | 138 | SelfInfo parseSelf(const std::string& json) 139 | { 140 | return s11n::parseJson(json); 141 | } 142 | 143 | std::map parseChecks(const std::string& json) 144 | { 145 | return s11n::parseJson>(json); 146 | } 147 | 148 | std::map parseServices(const std::string& json) 149 | { 150 | return s11n::parseJson>(json); 151 | } 152 | 153 | std::string checkRegistrationJson(const CheckRegistrationData& check) 154 | { 155 | using s11n::Json; 156 | 157 | Json::object o { 158 | { "ID", check.id }, 159 | { "Name", check.name }, 160 | { "Notes", check.notes } 161 | }; 162 | if (check.deregisterCriticalServiceAfter != decltype(check.deregisterCriticalServiceAfter)::zero()) { 163 | o["DeregisterCriticalServiceAfter"] = to_json(check.deregisterCriticalServiceAfter); 164 | } 165 | 166 | save(o, check.params); 167 | 168 | return Json(std::move(o)).dump(); 169 | } 170 | 171 | std::string serviceRegistrationJson(const ServiceRegistrationData& service) 172 | { 173 | using s11n::Json; 174 | 175 | Json::object o { 176 | { "ID", service.id }, 177 | { "Name", service.name }, 178 | { "Tags", to_json(service.tags) }, 179 | { "Meta", to_json(service.meta) }, 180 | { "Address", service.address }, 181 | { "Port", service.port } 182 | }; 183 | 184 | if (service.check) 185 | { 186 | Json::object check_o { 187 | { "Notes", service.check->notes }, 188 | }; 189 | 190 | if (service.check->deregisterCriticalServiceAfter != decltype(service.check->deregisterCriticalServiceAfter)::zero()) { 191 | check_o["DeregisterCriticalServiceAfter"] = to_json(service.check->deregisterCriticalServiceAfter); 192 | } 193 | save(check_o, service.check->params); 194 | 195 | o["Check"] = check_o; 196 | } 197 | 198 | return Json(std::move(o)).dump(); 199 | } 200 | } 201 | }} 202 | -------------------------------------------------------------------------------- /include/ppconsul/catalog.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #pragma once 8 | 9 | #include "ppconsul/consul.h" 10 | #include "ppconsul/helpers.h" 11 | #include "ppconsul/types.h" 12 | 13 | 14 | namespace ppconsul { namespace catalog { 15 | 16 | using NodeServices = std::pair>; 17 | 18 | using NodeService = std::pair; 19 | 20 | namespace kw { 21 | using ppconsul::kw::consistency; 22 | using ppconsul::kw::block_for; 23 | using ppconsul::kw::dc; 24 | using ppconsul::kw::tag; 25 | 26 | PPCONSUL_KEYWORD(near, std::string); 27 | 28 | namespace groups { 29 | KWARGS_KEYWORDS_GROUP(get, (consistency, dc, block_for)) 30 | } 31 | } 32 | 33 | namespace impl { 34 | StringList parseDatacenters(const std::string& json); 35 | std::vector parseNodes(const std::string& json); 36 | NodeServices parseNode(const std::string& json); 37 | std::map parseServices(const std::string& json); 38 | std::vector parseService(const std::string& json); 39 | } 40 | 41 | class Catalog 42 | { 43 | public: 44 | // Allowed parameters: 45 | // - consistency - default consistency for requests that support it 46 | // - dc - default dc for requests that support it 47 | template> 48 | explicit Catalog(Consul& consul, const Params&... params) 49 | : m_consul(consul) 50 | , m_defaultConsistency(kwargs::get_opt(kw::consistency, Consistency::Default, params...)) 51 | , m_defaultDc(kwargs::get_opt(kw::dc, std::string(), params...)) 52 | { 53 | KWARGS_CHECK_IN_LIST(Params, (kw::consistency, kw::dc)) 54 | } 55 | 56 | StringList datacenters() const 57 | { 58 | return impl::parseDatacenters(m_consul.get("/v1/catalog/datacenters")); 59 | } 60 | 61 | // Result contains both headers and data. 62 | // Allowed parameters: 63 | // - near 64 | // - groups::get 65 | template> 66 | Response> nodes(WithHeaders, const Params&... params) const; 67 | 68 | // Result contains data only. 69 | // Allowed parameters: 70 | // - near 71 | // - groups::get 72 | template> 73 | std::vector nodes(const Params&... params) const 74 | { 75 | return std::move(nodes(withHeaders, params...).data()); 76 | } 77 | 78 | // If node does not exist, function returns invalid node with empty serivces map 79 | // Result contains both headers and data. 80 | // Allowed parameters: 81 | // - groups::get 82 | template> 83 | Response node(WithHeaders, const std::string& name, const Params&... params) const; 84 | 85 | // If node does not exist, function returns invalid node with empty serivces map 86 | // Result contains data only. 87 | // Allowed parameters: 88 | // - groups::get 89 | template> 90 | NodeServices node(const std::string& name, const Params&... params) const 91 | { 92 | return std::move(node(withHeaders, name, params...).data()); 93 | } 94 | 95 | // Result contains both headers and data. 96 | // Allowed parameters: 97 | // - groups::get 98 | template> 99 | Response> services(WithHeaders, const Params&... params) const; 100 | 101 | // Result contains data only. 102 | // Allowed parameters: 103 | // - groups::get 104 | template> 105 | std::map services(const Params&... params) const 106 | { 107 | return std::move(services(withHeaders, params...).data()); 108 | } 109 | 110 | // Result contains both headers and data. 111 | // Allowed parameters: 112 | // - tag 113 | // - near 114 | // - groups::get 115 | template> 116 | Response> service(WithHeaders, const std::string& name, const Params&... params) const; 117 | 118 | // Result contains data only. 119 | // Allowed parameters: 120 | // - tag 121 | // - near 122 | // - groups::get 123 | template> 124 | std::vector service(const std::string& name, const Params&... params) const 125 | { 126 | return std::move(service(withHeaders, name, params...).data()); 127 | } 128 | 129 | private: 130 | Consul& m_consul; 131 | 132 | Consistency m_defaultConsistency; 133 | std::string m_defaultDc; 134 | }; 135 | 136 | 137 | // Implementation 138 | 139 | template 140 | Response> Catalog::nodes(WithHeaders, const Params&... params) const 141 | { 142 | KWARGS_CHECK_IN_LIST(Params, (kw::groups::get, kw::near)); 143 | auto r = m_consul.get(withHeaders, "/v1/catalog/nodes", 144 | kw::consistency = m_defaultConsistency, kw::dc = m_defaultDc, 145 | params...); 146 | return makeResponse(r.headers(), impl::parseNodes(r.data())); 147 | } 148 | 149 | template 150 | Response Catalog::node(WithHeaders, const std::string& name, const Params&... params) const 151 | { 152 | KWARGS_CHECK_IN_LIST(Params, (kw::groups::get)); 153 | auto r = m_consul.get(withHeaders, "/v1/catalog/node/" + helpers::encodeUrl(name), 154 | kw::consistency = m_defaultConsistency, kw::dc = m_defaultDc, 155 | params...); 156 | return makeResponse(r.headers(), impl::parseNode(r.data())); 157 | } 158 | 159 | template 160 | Response> Catalog::services(WithHeaders, const Params&... params) const 161 | { 162 | KWARGS_CHECK_IN_LIST(Params, (kw::groups::get)); 163 | auto r = m_consul.get(withHeaders, "/v1/catalog/services", 164 | kw::consistency = m_defaultConsistency, kw::dc = m_defaultDc, 165 | params...); 166 | return makeResponse(r.headers(), impl::parseServices(r.data())); 167 | } 168 | 169 | template 170 | Response> Catalog::service(WithHeaders, const std::string& name, const Params&... params) const 171 | { 172 | KWARGS_CHECK_IN_LIST(Params, (kw::groups::get, kw::tag, kw::near)); 173 | auto r = m_consul.get(withHeaders, "/v1/catalog/service/" + helpers::encodeUrl(name), 174 | kw::consistency = m_defaultConsistency, kw::dc = m_defaultDc, 175 | params...); 176 | return makeResponse(r.headers(), impl::parseService(r.data())); 177 | } 178 | 179 | }} 180 | -------------------------------------------------------------------------------- /src/kv.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | 8 | #include "ppconsul/kv.h" 9 | #include "s11n.h" 10 | 11 | 12 | namespace ppconsul { namespace kv { 13 | 14 | void load(const s11n::Json& src, KeyValue& dst) 15 | { 16 | using s11n::load; 17 | 18 | std::string value; 19 | 20 | load(src, dst.createIndex, "CreateIndex"); 21 | load(src, dst.modifyIndex, "ModifyIndex"); 22 | load(src, dst.lockIndex, "LockIndex"); 23 | load(src, dst.key, "Key"); 24 | load(src, dst.flags, "Flags"); 25 | load(src, value, "Value"); 26 | dst.value = helpers::decodeBase64(value); 27 | load(src, dst.session, "Session"); 28 | } 29 | 30 | struct TxnResults 31 | { 32 | std::vector results; 33 | }; 34 | 35 | void load(const s11n::Json &src, TxnResults& dst) 36 | { 37 | using s11n::load; 38 | 39 | const auto &arr = src["Results"].array_items(); 40 | auto &res = dst.results; 41 | res.reserve(arr.size()); 42 | for (const auto &item : arr) { 43 | res.emplace_back(); 44 | load(item["KV"], res.back()); 45 | } 46 | } 47 | 48 | struct TxnErrorResults 49 | { 50 | std::vector errors; 51 | }; 52 | 53 | void load(const s11n::Json &src, TxnError& dst) 54 | { 55 | using s11n::load; 56 | 57 | load(src, dst.opIndex, "OpIndex"); 58 | load(src, dst.what, "What"); 59 | } 60 | 61 | void load(const s11n::Json &src, TxnErrorResults& dst) 62 | { 63 | using s11n::load; 64 | 65 | const auto &errors_obj = src["Errors"]; 66 | if (errors_obj.is_null()) { 67 | return; 68 | } 69 | const auto &errors = errors_obj.array_items(); 70 | 71 | auto &res = dst.errors; 72 | res.reserve(errors.size()); 73 | for (const auto &error : errors) { 74 | res.emplace_back(); 75 | load(error, res.back()); 76 | } 77 | } 78 | 79 | namespace impl { 80 | 81 | StringList parseKeys(const std::string& resp) 82 | { 83 | return s11n::parseJson(resp); 84 | } 85 | 86 | std::vector parseValues(const std::string& resp) 87 | { 88 | return s11n::parseJson>(resp); 89 | } 90 | 91 | TxnAborted txnParseErrors(const std::string &resp) 92 | { 93 | return TxnAborted(s11n::parseJson(resp).errors); 94 | } 95 | 96 | std::vector txnParseValues(const std::string &resp) 97 | { 98 | return s11n::parseJson(resp).results; 99 | } 100 | 101 | class TxnOpAppender: public boost::static_visitor<> 102 | { 103 | public: 104 | TxnOpAppender(s11n::Json::array &arr) 105 | : m_arr(arr) 106 | {} 107 | 108 | void operator ()(const txn_ops::Set &op) const 109 | { 110 | append(s11n::Json::object{ 111 | {"Verb", "set"}, 112 | {"Key", op.key}, 113 | {"Value", helpers::encodeBase64(op.value)}, 114 | {"Flags", static_cast(op.flags)}, 115 | }); 116 | } 117 | 118 | void operator ()(const txn_ops::CompareSet &op) const 119 | { 120 | append(s11n::Json::object{ 121 | {"Verb", "cas"}, 122 | {"Key", op.key}, 123 | {"Value", helpers::encodeBase64(op.value)}, 124 | {"Index", static_cast(op.expectedIndex)}, 125 | {"Flags", static_cast(op.flags)}, 126 | }); 127 | } 128 | 129 | void operator ()(const txn_ops::Get &op) const 130 | { 131 | append(s11n::Json::object{ 132 | {"Verb", "get"}, 133 | {"Key", op.key}, 134 | }); 135 | } 136 | 137 | void operator ()(const txn_ops::GetAll &op) const 138 | { 139 | append(s11n::Json::object{ 140 | {"Verb", "get-tree"}, 141 | {"Key", op.keyPrefix}, 142 | }); 143 | } 144 | 145 | void operator ()(const txn_ops::CheckIndex &op) const 146 | { 147 | append(s11n::Json::object{ 148 | {"Verb", "check-index"}, 149 | {"Key", op.key}, 150 | {"Index", static_cast(op.expectedIndex)}, 151 | }); 152 | } 153 | 154 | void operator ()(const txn_ops::CheckNotExists &op) const 155 | { 156 | append(s11n::Json::object{ 157 | {"Verb", "check-not-exists"}, 158 | {"Key", op.key}, 159 | }); 160 | } 161 | 162 | void operator ()(const txn_ops::Erase &op) const 163 | { 164 | append(s11n::Json::object{ 165 | {"Verb", "delete"}, 166 | {"Key", op.key}, 167 | }); 168 | } 169 | 170 | void operator ()(const txn_ops::EraseAll &op) const 171 | { 172 | append(s11n::Json::object{ 173 | {"Verb", "delete-tree"}, 174 | {"Key", op.keyPrefix}, 175 | }); 176 | } 177 | 178 | void operator ()(const txn_ops::CompareErase &op) const 179 | { 180 | append(s11n::Json::object{ 181 | {"Verb", "delete-cas"}, 182 | {"Key", op.key}, 183 | {"Index", static_cast(op.expectedIndex)}, 184 | }); 185 | } 186 | 187 | void operator ()(const txn_ops::Lock &op) const 188 | { 189 | append(s11n::Json::object{ 190 | {"Verb", "lock"}, 191 | {"Key", op.key}, 192 | {"Value", helpers::encodeBase64(op.value)}, 193 | {"Flags", static_cast(op.flags)}, 194 | {"Session", op.session}, 195 | }); 196 | } 197 | 198 | void operator ()(const txn_ops::Unlock &op) const 199 | { 200 | append(s11n::Json::object{ 201 | {"Verb", "unlock"}, 202 | {"Key", op.key}, 203 | {"Value", helpers::encodeBase64(op.value)}, 204 | {"Flags", static_cast(op.flags)}, 205 | {"Session", op.session}, 206 | }); 207 | } 208 | 209 | void operator ()(const txn_ops::CheckSession &op) const 210 | { 211 | append(s11n::Json::object{ 212 | {"Verb", "check-session"}, 213 | {"Key", op.key}, 214 | {"Session", op.session}, 215 | }); 216 | } 217 | 218 | private: 219 | s11n::Json::array &m_arr; 220 | 221 | void append(s11n::Json::object &&o) const 222 | { 223 | m_arr.emplace_back(s11n::Json::object{{"KV", std::move(o)}}); 224 | } 225 | 226 | using FlagsType = double; 227 | using IndexType = double; 228 | }; 229 | 230 | std::string txnBodyJson(const std::vector &ops) 231 | { 232 | s11n::Json::array arr; 233 | arr.reserve(ops.size()); 234 | for (const auto &op : ops) { 235 | boost::apply_visitor(TxnOpAppender(arr), op); 236 | } 237 | return s11n::Json(std::move(arr)).dump(); 238 | } 239 | } 240 | }} 241 | -------------------------------------------------------------------------------- /rationales.md: -------------------------------------------------------------------------------- 1 | Notes about the project rationals 2 | ================================= 3 | 4 | Just some notes for future me about the project decisions was made. 5 | 6 | Keywords naming and namespace rationale 7 | --------------------------------------- 8 | 9 | How to name parameters namespace? 10 | a1) ppconsul::keywords::service_id !! too detailed 11 | * a2) ppconsul::keywords::id !!! 12 | b1) ppconsul::_service_id ! not clear that it's about keywords 13 | b2) ppconsul::_id ! 14 | 15 | Registration interface rationale 2 16 | ---------------------------------- 17 | 18 | 1) Registration object + extra keywords vs flat set of keywords 19 | 20 | agent.registerCheck( 21 | keywords::name = "", 22 | keywords::check*** = agent::ScriptCheck{"ccc.sh", std::chrono::seconds(20)}, 23 | keywords::notes = "", 24 | keywords::id = "", 25 | keywords::token = "" 26 | ); 27 | 28 | agent.registerCheck( 29 | { 30 | keywords::name = "", 31 | keywords::check*** = agent::ScriptCheck{"ccc.sh", std::chrono::seconds(20)}, 32 | keywords::notes = "", 33 | keywords::id = "" 34 | }, 35 | keywords::token = "" 36 | ); 37 | 38 | 2) Use keyword arguments for all field (a) s vs only for optional ones (b) 39 | 40 | a) 41 | agent.registerCheck( 42 | { 43 | keywords::name = "", 44 | keywords::check*** = agent::ScriptCheck{"ccc.sh", std::chrono::seconds(20)}, 45 | keywords::notes = "", 46 | keywords::id = "" 47 | }, 48 | keywords::token = "" 49 | ); 50 | or even 51 | agent.registerCheck( 52 | { 53 | keywords::name = "", 54 | keywords::script_check = {"ccc.sh", std::chrono::seconds(20)}, 55 | keywords::notes = "", 56 | keywords::id = "" 57 | }, 58 | keywords::token = "" 59 | ); 60 | 61 | b) 62 | agent.registerCheck( 63 | { 64 | "name", 65 | agent::ScriptCheck{"ccc.sh", std::chrono::seconds(20)}, 66 | keywords::notes = "", 67 | keywords::id = "" 68 | }, 69 | keywords::token = "" 70 | ); 71 | 72 | 73 | Registration interface rationale 74 | -------------------------------- 75 | 76 | Checks: 77 | All checks has "name", "id" = name, "notes" = "", "name" = "service:" for a service checks. 78 | 79 | * TTL: "ttl" 80 | * Script: "script" + "interval" [+ "timeout"] 81 | * HTTP: "http" + "interval" [+ "timeout"] 82 | * Docker: "docker_container_id" + "shell" + "script" + "interval" 83 | 84 | 85 | ```code=cpp 86 | // New style 1: all optional params are keywords 87 | agent.registerCheck("check1", params::ttl=std::chrono::seconds(10)); 88 | agent.registerCheck("check1", params::notes="", params::ttl=std::chrono::seconds(10)); 89 | agent.registerCheck("check1", params::notes="", params::script="bla-bla.py", params::interval=std::chrono::seconds(10)); 90 | agent.registerService("service1", params::tags={"udp", "service1", "service2"}, params::ttl=std::chrono::seconds(10)); 91 | agent.registerService("service1", params::tags={"udp", "service1", "service2"}, params::script="bla-bla.py", params::interval=std::chrono::seconds(10)); 92 | 93 | // New style 2: structures with keywords 94 | agent.registerCheck({"check1", params::notes="bla-bla"}, HttpCheck("http://localhost/test", std::chrono::seconds(10), params::tiemout=std::chrono::seconds(2))); 95 | agent.registerService({"service1", params::tags={"udp", "service1", "service2"}}); 96 | // ... 97 | 98 | // New Style 3: higher granulated keywords 99 | agent.registerCheck("check1", params::notes="", params::ttl_check={std::chrono::seconds(10)}); 100 | agent.registerCheck("check1", params::notes="", params::script_check={params::script="bla-bla.py", params::interval=std::chrono::seconds(10)}); 101 | agent.registerService("service1", params::tags={"udp", "service1", "service2"}); 102 | agent.registerService("service1", params::tags={"udp", "service1", "service2"}, params::ttl_check={std::chrono::seconds(10)}); 103 | agent.registerService("service1", params::tags={"udp", "service1", "service2"}, params::script_check={"bla-bla.py", std::chrono::seconds(10), params::timeout=std::chrono::seconds(2)}); 104 | 105 | // New Style 4: higher granulated keywords using variant 106 | agent.registerCheck("check1", TtlCheck{std::chrono::seconds(10)}); 107 | agent.registerCheck("check1", ScriptCheck{"bla-bla.py", std::chrono::seconds(10), params::timeout=std::chrono::seconds(2)}, params::notes=""); 108 | agent.registerService("service1", params::tags={"udp", "service1", "service2"}); 109 | agent.registerService("service1", params::tags={"udp", "service1", "service2"}, params::check=TtlCheck{std::chrono::seconds(10)}); 110 | agent.registerService("service1", params::tags={"udp", "service1", "service2"}, params::check=ScriptCheck{"bla-bla.py", std::chrono::seconds(10), params::timeout=std::chrono::seconds(2)}); 111 | ``` 112 | 113 | Option 4 seems the most descriptive, clear and intuitive. 114 | 115 | 116 | 117 | 118 | Qs: 119 | 1) Where to put data types - under the root namespace or under the endpoint's namespace? 120 | - some types are shared between endpoints, some types are different (with the same name) in different endpoints 121 | - probably better unique names should be found, so all types can be placed to the root namespace? 122 | 2) Why are endpoint namespaces needed at all? 123 | - To avoid conflicts between entities related to different endpoints 124 | 125 | 3) Typed parameters for check params, yes or no? 126 | 127 | ppconsul::agent::params::check_params = ppconsul::agent::TtlCheckParams{std::chrono::minutes(5)} 128 | ppconsul::agent::params::check_params = ppconsul::agent::ScriptCheckParams{"run.sh", std::chrono::minutes(5)} 129 | 130 | vs 131 | 132 | ppconsul::agent::params::ttl_check = {std::chrono::minutes(5)} 133 | ppconsul::agent::params::scipt_check = {"run.sh", std::chrono::minutes(5), "bla-bla"} 134 | 135 | 136 | 5) Has user to provide a RegistrationData object? 137 | a) agent.registerService() 138 | 139 | 140 | 141 | Ex: 142 | 1) current approach 143 | ppconsul::agent::Agent agent(consul); 144 | 145 | agent.registerService({ 146 | name, 147 | ppconsul::kw::check = ppconsul::agent::TtlCheckParams{std::chrono::minutes(5)}, 148 | ppconsul::kw::params::id = "id" 149 | }); 150 | 151 | Long, collision between structures related to a one endpoint is possible 152 | 153 | 2) remove endpoint namespace 154 | ppconsul::Agent agent(consul); 155 | 156 | agent.registerService({ 157 | name, 158 | ppconsul::params::check = ppconsul::agent::TtlCheckParams{std::chrono::minutes(5)}, 159 | ppconsul::params::id = "id" 160 | }); 161 | 162 | Still noisy, collisions between endpoints 163 | 164 | 3) instead of endpoint's, use a type's namespace 165 | ppconsul::agent::Agent agent(consul); 166 | 167 | agent.registerService({ 168 | name, 169 | ppconsul::service::check = ppconsul::agent::TtlCheckParams{std::chrono::minutes(5)}, 170 | ppconsul::service::id = "id" 171 | }); 172 | 173 | Name is not obvious at all. There is confusion between different approach to name endpoints 174 | 175 | 4) 176 | ppconsul::agent::Agent agent(consul); 177 | 178 | agent.registerService({ 179 | name, 180 | ppconsul::agent::service_check = ppconsul::agent::TtlCheckParams{std::chrono::minutes(5)}, 181 | ppconsul::agent::service_id = "id" 182 | }); 183 | 184 | -------------------------------------------------------------------------------- /include/ppconsul/health.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #pragma once 8 | 9 | #include "ppconsul/consul.h" 10 | #include "ppconsul/helpers.h" 11 | #include "ppconsul/types.h" 12 | #include 13 | 14 | 15 | namespace ppconsul { namespace health { 16 | 17 | namespace kw { 18 | using ppconsul::kw::consistency; 19 | using ppconsul::kw::block_for; 20 | using ppconsul::kw::dc; 21 | using ppconsul::kw::tag; 22 | 23 | PPCONSUL_KEYWORD(passing, bool); 24 | PPCONSUL_KEYWORD(near, std::string); 25 | 26 | namespace groups { 27 | KWARGS_KEYWORDS_GROUP(get, (consistency, dc, block_for)); 28 | } 29 | } 30 | 31 | using NodeServiceChecks = std::tuple>; 32 | 33 | namespace impl { 34 | std::vector parseCheckInfos(const std::string& json); 35 | std::vector parseService(const std::string& json); 36 | std::string to_string(CheckStatus state); 37 | } 38 | 39 | 40 | class Health 41 | { 42 | public: 43 | // Allowed parameters: 44 | // - consistency - default consistency for requests that support it 45 | // - dc - default dc for requests that support it 46 | template> 47 | explicit Health(Consul& consul, const Params&... params) 48 | : m_consul(consul) 49 | , m_defaultConsistency(kwargs::get_opt(kw::consistency, Consistency::Default, params...)) 50 | , m_defaultDc(kwargs::get_opt(kw::dc, std::string(), params...)) 51 | { 52 | KWARGS_CHECK_IN_LIST(Params, (kw::consistency, kw::dc)) 53 | } 54 | 55 | // Result contains both headers and data. 56 | // Allowed parameters: 57 | // - groups::get 58 | template> 59 | Response> node(WithHeaders, const std::string& name, const Params&... params) const; 60 | 61 | // Result contains data only. 62 | // Allowed parameters: 63 | // - groups::get 64 | template> 65 | std::vector node(const std::string& name, const Params&... params) const 66 | { 67 | return std::move(node(withHeaders, name, params...).data()); 68 | } 69 | 70 | // Result contains both headers and data. 71 | // Allowed parameters: 72 | // - groups::get 73 | // - kw::near 74 | template> 75 | Response> checks(WithHeaders, const std::string& serviceName, const Params&... params) const; 76 | 77 | // Result contains data only. 78 | // Allowed parameters: 79 | // - groups::get 80 | // - kw::near 81 | template> 82 | std::vector checks(const std::string& serviceName, const Params&... params) const 83 | { 84 | return std::move(checks(withHeaders, serviceName, params...).data()); 85 | } 86 | 87 | // Result contains both headers and data. 88 | // Allowed parameters: 89 | // - groups::get 90 | // - kw::tag 91 | // - kw::passing 92 | // - kw::near 93 | template> 94 | Response> service(WithHeaders, const std::string& serviceName, const Params&... params) const; 95 | 96 | // Result contains data only. 97 | // Allowed parameters: 98 | // - groups::get 99 | // - kw::tag 100 | // - kw::passing 101 | // - kw::near 102 | template> 103 | std::vector service(const std::string& serviceName, const Params&... params) const 104 | { 105 | return std::move(service(withHeaders, serviceName, params...).data()); 106 | } 107 | 108 | // Returns the checks in specified state 109 | // Result contains both headers and data. 110 | // Allowed parameters: 111 | // - groups::get 112 | // - kw::near 113 | template> 114 | Response> state(WithHeaders, CheckStatus state, const Params&... params) const 115 | { 116 | return state_impl(impl::to_string(state), params...); 117 | } 118 | 119 | // Returns the checks in specified state 120 | // Result contains data only. 121 | // Allowed parameters: 122 | // - groups::get 123 | // - kw::near 124 | template> 125 | std::vector state(CheckStatus state, const Params&... params) const 126 | { 127 | return std::move(this->state(withHeaders, state, params...).data()); 128 | } 129 | 130 | // Returns all the checks 131 | // Result contains both headers and data. 132 | // Allowed parameters: 133 | // - groups::get 134 | template> 135 | Response> state(WithHeaders, const Params&... params) const 136 | { 137 | return state_impl("any", params...); 138 | } 139 | 140 | // Returns all the checks 141 | // Result contains data only. 142 | // Allowed parameters: 143 | // - groups::get 144 | template> 145 | std::vector state(const Params&... params) const 146 | { 147 | return std::move(state(withHeaders, params...).data()); 148 | } 149 | 150 | private: 151 | template> 152 | Response> state_impl(const std::string& state, const Params&... params) const; 153 | 154 | Consul& m_consul; 155 | 156 | Consistency m_defaultConsistency; 157 | std::string m_defaultDc; 158 | }; 159 | 160 | 161 | // Implementation 162 | 163 | inline std::string impl::to_string(CheckStatus state) 164 | { 165 | switch (state) 166 | { 167 | case CheckStatus::Unknown: 168 | return "unknown"; 169 | case CheckStatus::Passing: 170 | return "passing"; 171 | case CheckStatus::Warning: 172 | return "warning"; 173 | case CheckStatus::Critical: 174 | return "critical"; 175 | default: 176 | throw std::logic_error("Wrong CheckStatus value"); 177 | } 178 | } 179 | 180 | template 181 | Response> Health::node(WithHeaders, const std::string& name, const Params&... params) const 182 | { 183 | KWARGS_CHECK_IN_LIST(Params, (kw::groups::get)); 184 | auto r = m_consul.get(withHeaders, "/v1/health/node/" + helpers::encodeUrl(name), 185 | kw::consistency = m_defaultConsistency, kw::dc = m_defaultDc, 186 | params...); 187 | return makeResponse(r.headers(), impl::parseCheckInfos(r.data())); 188 | } 189 | 190 | template 191 | Response> Health::checks(WithHeaders, const std::string& serviceName, const Params&... params) const 192 | { 193 | KWARGS_CHECK_IN_LIST(Params, (kw::groups::get, kw::near)); 194 | auto r = m_consul.get(withHeaders, "/v1/health/checks/" + helpers::encodeUrl(serviceName), 195 | kw::consistency = m_defaultConsistency, kw::dc = m_defaultDc, 196 | params...); 197 | return makeResponse(r.headers(), impl::parseCheckInfos(r.data())); 198 | } 199 | 200 | template 201 | Response> Health::service(WithHeaders, const std::string& serviceName, const Params&... params) const 202 | { 203 | KWARGS_CHECK_IN_LIST(Params, (kw::groups::get, kw::tag, kw::passing, kw::near)); 204 | auto r = m_consul.get(withHeaders, "/v1/health/service/" + helpers::encodeUrl(serviceName), 205 | kw::consistency = m_defaultConsistency, kw::dc = m_defaultDc, 206 | params...); 207 | return makeResponse(r.headers(), impl::parseService(r.data())); 208 | } 209 | 210 | template 211 | Response> Health::state_impl(const std::string& state, const Params&... params) const 212 | { 213 | KWARGS_CHECK_IN_LIST(Params, (kw::groups::get, kw::near)); 214 | auto r = m_consul.get(withHeaders, "/v1/health/state/" + state, 215 | kw::consistency = m_defaultConsistency, kw::dc = m_defaultDc, 216 | params...); 217 | return makeResponse(r.headers(), impl::parseCheckInfos(r.data())); 218 | } 219 | 220 | }} 221 | -------------------------------------------------------------------------------- /ext/json11/json11.hpp: -------------------------------------------------------------------------------- 1 | /* json11 2 | * 3 | * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. 4 | * 5 | * The core object provided by the library is json11::Json. A Json object represents any JSON 6 | * value: null, bool, number (int or double), string (std::string), array (std::vector), or 7 | * object (std::map). 8 | * 9 | * Json objects act like values: they can be assigned, copied, moved, compared for equality or 10 | * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and 11 | * Json::parse (static) to parse a std::string as a Json object. 12 | * 13 | * Internally, the various types of Json object are represented by the JsonValue class 14 | * hierarchy. 15 | * 16 | * A note on numbers - JSON specifies the syntax of number formatting but not its semantics, 17 | * so some JSON implementations distinguish between integers and floating-point numbers, while 18 | * some don't. In json11, we choose the latter. Because some JSON implementations (namely 19 | * Javascript itself) treat all numbers as the same type, distinguishing the two leads 20 | * to JSON that will be *silently* changed by a round-trip through those implementations. 21 | * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also 22 | * provides integer helpers. 23 | * 24 | * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the 25 | * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64 26 | * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch 27 | * will be exact for +/- 275 years.) 28 | */ 29 | 30 | /* Copyright (c) 2013 Dropbox, Inc. 31 | * 32 | * Permission is hereby granted, free of charge, to any person obtaining a copy 33 | * of this software and associated documentation files (the "Software"), to deal 34 | * in the Software without restriction, including without limitation the rights 35 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 36 | * copies of the Software, and to permit persons to whom the Software is 37 | * furnished to do so, subject to the following conditions: 38 | * 39 | * The above copyright notice and this permission notice shall be included in 40 | * all copies or substantial portions of the Software. 41 | * 42 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 45 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 47 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 48 | * THE SOFTWARE. 49 | */ 50 | 51 | #pragma once 52 | 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | 59 | #ifdef _MSC_VER 60 | #if _MSC_VER <= 1800 // VS 2013 61 | #ifndef noexcept 62 | #define noexcept throw() 63 | #endif 64 | 65 | #ifndef snprintf 66 | #define snprintf _snprintf_s 67 | #endif 68 | #endif 69 | #endif 70 | 71 | namespace json11 { 72 | 73 | enum JsonParse { 74 | STANDARD, COMMENTS 75 | }; 76 | 77 | class JsonValue; 78 | 79 | class Json final { 80 | public: 81 | // Types 82 | enum Type { 83 | NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT 84 | }; 85 | 86 | // Array and object typedefs 87 | typedef std::vector array; 88 | typedef std::map object; 89 | 90 | // Constructors for the various types of JSON value. 91 | Json() noexcept; // NUL 92 | Json(std::nullptr_t) noexcept; // NUL 93 | Json(double value); // NUMBER 94 | Json(int value); // NUMBER 95 | Json(bool value); // BOOL 96 | Json(const std::string &value); // STRING 97 | Json(std::string &&value); // STRING 98 | Json(const char * value); // STRING 99 | Json(const array &values); // ARRAY 100 | Json(array &&values); // ARRAY 101 | Json(const object &values); // OBJECT 102 | Json(object &&values); // OBJECT 103 | 104 | // Implicit constructor: anything with a to_json() function. 105 | template 106 | Json(const T & t) : Json(t.to_json()) {} 107 | 108 | // Implicit constructor: map-like objects (std::map, std::map, etc) 109 | template ().begin()->first)>::value 111 | && std::is_constructible().begin()->second)>::value, 112 | int>::type = 0> 113 | Json(const M & m) : Json(object(m.begin(), m.end())) {} 114 | 115 | // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc) 116 | template ().begin())>::value, 118 | int>::type = 0> 119 | Json(const V & v) : Json(array(v.begin(), v.end())) {} 120 | 121 | // This prevents Json(some_pointer) from accidentally producing a bool. Use 122 | // Json(bool(some_pointer)) if that behavior is desired. 123 | Json(void *) = delete; 124 | 125 | // Accessors 126 | Type type() const; 127 | 128 | bool is_null() const { return type() == NUL; } 129 | bool is_number() const { return type() == NUMBER; } 130 | bool is_bool() const { return type() == BOOL; } 131 | bool is_string() const { return type() == STRING; } 132 | bool is_array() const { return type() == ARRAY; } 133 | bool is_object() const { return type() == OBJECT; } 134 | 135 | // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not 136 | // distinguish between integer and non-integer numbers - number_value() and int_value() 137 | // can both be applied to a NUMBER-typed object. 138 | double number_value() const; 139 | int int_value() const; 140 | 141 | // Return the enclosed value if this is a boolean, false otherwise. 142 | bool bool_value() const; 143 | // Return the enclosed string if this is a string, "" otherwise. 144 | const std::string &string_value() const; 145 | // Return the enclosed std::vector if this is an array, or an empty vector otherwise. 146 | const array &array_items() const; 147 | // Return the enclosed std::map if this is an object, or an empty map otherwise. 148 | const object &object_items() const; 149 | 150 | // Return a reference to arr[i] if this is an array, Json() otherwise. 151 | const Json & operator[](size_t i) const; 152 | // Return a reference to obj[key] if this is an object, Json() otherwise. 153 | const Json & operator[](const std::string &key) const; 154 | 155 | // Serialize. 156 | void dump(std::string &out) const; 157 | std::string dump() const { 158 | std::string out; 159 | dump(out); 160 | return out; 161 | } 162 | 163 | // Parse. If parse fails, return Json() and assign an error message to err. 164 | static Json parse(const std::string & in, 165 | std::string & err, 166 | JsonParse strategy = JsonParse::STANDARD); 167 | static Json parse(const char * in, 168 | std::string & err, 169 | JsonParse strategy = JsonParse::STANDARD) { 170 | if (in) { 171 | return parse(std::string(in), err, strategy); 172 | } else { 173 | err = "null input"; 174 | return nullptr; 175 | } 176 | } 177 | // Parse multiple objects, concatenated or separated by whitespace 178 | static std::vector parse_multi( 179 | const std::string & in, 180 | std::string::size_type & parser_stop_pos, 181 | std::string & err, 182 | JsonParse strategy = JsonParse::STANDARD); 183 | 184 | static inline std::vector parse_multi( 185 | const std::string & in, 186 | std::string & err, 187 | JsonParse strategy = JsonParse::STANDARD) { 188 | std::string::size_type parser_stop_pos; 189 | return parse_multi(in, parser_stop_pos, err, strategy); 190 | } 191 | 192 | bool operator== (const Json &rhs) const; 193 | bool operator< (const Json &rhs) const; 194 | bool operator!= (const Json &rhs) const { return !(*this == rhs); } 195 | bool operator<= (const Json &rhs) const { return !(rhs < *this); } 196 | bool operator> (const Json &rhs) const { return (rhs < *this); } 197 | bool operator>= (const Json &rhs) const { return !(*this < rhs); } 198 | 199 | /* has_shape(types, err) 200 | * 201 | * Return true if this is a JSON object and, for each item in types, has a field of 202 | * the given type. If not, return false and set err to a descriptive message. 203 | */ 204 | typedef std::initializer_list> shape; 205 | bool has_shape(const shape & types, std::string & err) const; 206 | 207 | private: 208 | std::shared_ptr m_ptr; 209 | }; 210 | 211 | // Internal class hierarchy - JsonValue objects are not exposed to users of this API. 212 | class JsonValue { 213 | protected: 214 | friend class Json; 215 | friend class JsonInt; 216 | friend class JsonDouble; 217 | virtual Json::Type type() const = 0; 218 | virtual bool equals(const JsonValue * other) const = 0; 219 | virtual bool less(const JsonValue * other) const = 0; 220 | virtual void dump(std::string &out) const = 0; 221 | virtual double number_value() const; 222 | virtual int int_value() const; 223 | virtual bool bool_value() const; 224 | virtual const std::string &string_value() const; 225 | virtual const Json::array &array_items() const; 226 | virtual const Json &operator[](size_t i) const; 227 | virtual const Json::object &object_items() const; 228 | virtual const Json &operator[](const std::string &key) const; 229 | virtual ~JsonValue() {} 230 | }; 231 | 232 | } // namespace json11 233 | -------------------------------------------------------------------------------- /src/curl/http_client.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2020 Andrey Upadyshev 2 | // 3 | // Use, modification and distribution are subject to the 4 | // Boost Software License, Version 1.0. (See accompanying file 5 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include "http_client.h" 8 | #include "../http_helpers.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #if !defined PPCONSUL_USE_BOOST_REGEX 17 | #include 18 | #else 19 | #include 20 | #endif 21 | 22 | #if (LIBCURL_VERSION_MAJOR < 7) 23 | #error "Where did you get such an ancient libcurl?" 24 | #endif 25 | 26 | // CURLOPT_SSL_VERIFYSTATUS was added in libcurl 7.41.0 27 | // https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYSTATUS.html 28 | #if (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR < 41) 29 | #define PPCONSUL_DISABLE_SSL_VERIFYSTATUS 30 | #endif 31 | 32 | 33 | namespace ppconsul { namespace curl { 34 | 35 | using namespace ppconsul::http::impl; 36 | 37 | namespace { 38 | #if !defined PPCONSUL_USE_BOOST_REGEX 39 | using std::regex; 40 | using std::regex_match; 41 | using std::cmatch; 42 | #else 43 | using boost::regex; 44 | using boost::regex_match; 45 | using boost::cmatch; 46 | #endif 47 | 48 | const regex g_statusLineRegex(R"***(HTTP\/1\.1 +(\d\d\d) +(.*)\r\n)***"); 49 | const regex g_consulHeaderLineRegex(R"***(([^:]+): +(.+)\r\n)***"); 50 | 51 | inline bool parseStatus(http::Status& status, const char *buf, size_t size) 52 | { 53 | cmatch match; 54 | if (!regex_match(buf, buf + size, match, g_statusLineRegex)) 55 | return false; 56 | status = http::Status(std::atol(match[1].str().c_str()), match[2].str()); 57 | return true; 58 | } 59 | 60 | void throwCurlError(CURLcode code, const char *err) 61 | { 62 | if (code == CURLE_ABORTED_BY_CALLBACK) 63 | throw OperationAborted(); 64 | else if (code == CURLE_OPERATION_TIMEDOUT) 65 | throw RequestTimedOut(err); 66 | else 67 | throw std::runtime_error(std::string(err) + " (" + std::to_string(code) + ")"); 68 | } 69 | 70 | enum { Buffer_Size = 16384 }; 71 | 72 | using ReadContext = std::pair; 73 | 74 | size_t headerStatusCallback(char *ptr, size_t size_, size_t nitems, void *outputStatus) 75 | { 76 | const auto size = size_ * nitems; 77 | parseStatus(*static_cast(outputStatus), ptr, size); 78 | return size; 79 | } 80 | 81 | size_t headerCallback(char *ptr, size_t size_, size_t nitems, void *outputResponse_) 82 | { 83 | const auto size = size_ * nitems; 84 | auto outputResponse = reinterpret_cast(outputResponse_); 85 | 86 | if (parseStatus(std::get<0>(*outputResponse), ptr, size)) 87 | return size; 88 | 89 | // Parse headers 90 | cmatch match; 91 | if (!regex_match(const_cast(ptr), const_cast(ptr) +size, match, g_consulHeaderLineRegex)) 92 | return size; 93 | 94 | ResponseHeaders& headers = std::get<1>(*outputResponse); 95 | 96 | if (0 == match[1].compare(Index_Header_Name)) 97 | headers.m_index = uint64_headerValue(match[2].str().c_str()); 98 | else if (0 == match[1].compare(LastContact_Headers_Name)) 99 | headers.m_lastContact = std::chrono::milliseconds(uint64_headerValue(match[2].str().c_str())); 100 | else if (0 == match[1].compare(KnownLeader_Header_Name)) 101 | headers.m_knownLeader = bool_headerValue(match[2].str().c_str()); 102 | 103 | return size; 104 | } 105 | 106 | size_t writeCallback(char *ptr, size_t size_, size_t nitems, void *outputStr) 107 | { 108 | const auto size = size_ * nitems; 109 | static_cast(outputStr)->append(ptr, size); 110 | return size; 111 | } 112 | 113 | size_t readCallback(char *buffer, size_t size_, size_t nitems, void *readContext) 114 | { 115 | const auto ctx = static_cast(readContext); 116 | 117 | const auto remainingSize = ctx->first->size() - ctx->second; 118 | if (!remainingSize) 119 | return 0; 120 | 121 | auto size = (std::min)(size_ * nitems, remainingSize); 122 | memcpy(buffer, ctx->first->data() + ctx->second, size); 123 | ctx->second += size; 124 | return size; 125 | } 126 | 127 | #if (LIBCURL_VERSION_NUM >= 0x073200) 128 | int progressCallback(void *clientPtr, curl_off_t, curl_off_t, curl_off_t, curl_off_t) 129 | #else 130 | // old-style progress callback for curl <= 7.31.0 131 | int progressCallback(void *clientPtr, double, double, double, double) 132 | #endif 133 | { 134 | const auto* client = static_cast(clientPtr); 135 | return client->stopped(); 136 | } 137 | 138 | } 139 | 140 | CurlHeaderList makeCurlHeaderList(const http::RequestHeaders & headers) 141 | { 142 | CurlHeaderList header_list = nullptr; 143 | 144 | for (const auto & header : headers) 145 | { 146 | std::string record; 147 | record.reserve(header.first.size() + 2 + header.second.size()); // +2 for ": " 148 | record += header.first; 149 | record += ": "; 150 | record += header.second; 151 | 152 | auto ptr = curl_slist_append(header_list.get(), record.c_str()); 153 | if (!ptr) 154 | throw std::runtime_error("CURL headers append failed"); 155 | 156 | header_list.release(); 157 | header_list.reset(ptr); 158 | } 159 | 160 | return header_list; 161 | } 162 | 163 | std::unique_ptr CurlHttpClientFactory::operator() (const std::string& endpoint, 164 | const ppconsul::http::HttpClientConfig& config, 165 | std::function cancellationCallback) const 166 | { 167 | return std::unique_ptr(new CurlHttpClient(endpoint, config, cancellationCallback)); 168 | } 169 | 170 | 171 | CurlHttpClient::CurlHttpClient(const std::string& endpoint, 172 | const ppconsul::http::HttpClientConfig& config, 173 | const std::function& cancellationCallback) 174 | : m_cancellationCallback(cancellationCallback) 175 | , m_endpoint(endpoint) 176 | { 177 | m_handle.reset(curl_easy_init()); 178 | if (!m_handle) 179 | throw std::runtime_error("CURL handle creation failed"); 180 | 181 | if (auto err = curl_easy_setopt(handle(), CURLOPT_ERRORBUFFER, m_errBuffer)) 182 | throwCurlError(err, ""); 183 | 184 | if (m_cancellationCallback) 185 | { 186 | setopt(CURLOPT_NOPROGRESS, 0l); 187 | #if (LIBCURL_VERSION_NUM >= 0x073200) 188 | setopt(CURLOPT_XFERINFOFUNCTION, &progressCallback); 189 | setopt(CURLOPT_XFERINFODATA, this); 190 | #else 191 | setopt(CURLOPT_PROGRESSFUNCTION, &progressCallback); 192 | setopt(CURLOPT_PROGRESSDATA, this); 193 | #endif 194 | } 195 | else 196 | { 197 | setopt(CURLOPT_NOPROGRESS, 1l); 198 | } 199 | 200 | setopt(CURLOPT_WRITEFUNCTION, &writeCallback); 201 | setopt(CURLOPT_READFUNCTION, &readCallback); 202 | 203 | setopt(CURLOPT_NOSIGNAL, 1l); 204 | setopt(CURLOPT_TIMEOUT_MS, static_cast(config.requestTimeout.count())); 205 | setopt(CURLOPT_CONNECTTIMEOUT_MS, static_cast(config.connectTimeout.count())); 206 | 207 | setupTls(config.tls); 208 | } 209 | 210 | void CurlHttpClient::setupTls(const ppconsul::http::TlsConfig& tlsConfig) 211 | { 212 | if (!tlsConfig.cert.empty()) 213 | setopt(CURLOPT_SSLCERT, tlsConfig.cert.c_str()); 214 | if (!tlsConfig.certType.empty()) 215 | setopt(CURLOPT_CAPATH, tlsConfig.certType.c_str()); 216 | if (!tlsConfig.key.empty()) 217 | setopt(CURLOPT_SSLKEY, tlsConfig.key.c_str()); 218 | if (!tlsConfig.keyType.empty()) 219 | setopt(CURLOPT_CAPATH, tlsConfig.certType.c_str()); 220 | if (!tlsConfig.caPath.empty()) 221 | setopt(CURLOPT_CAPATH, tlsConfig.caPath.c_str()); 222 | if (!tlsConfig.caInfo.empty()) 223 | setopt(CURLOPT_CAINFO, tlsConfig.caInfo.c_str()); 224 | 225 | if (tlsConfig.keyPass && *tlsConfig.keyPass) 226 | setopt(CURLOPT_KEYPASSWD, tlsConfig.keyPass); 227 | 228 | setopt(CURLOPT_SSL_VERIFYPEER, tlsConfig.verifyPeer ? 1l : 0l); 229 | setopt(CURLOPT_SSL_VERIFYHOST, tlsConfig.verifyHost ? 2l : 0l); 230 | 231 | if (tlsConfig.verifyStatus) 232 | { 233 | #ifdef PPCONSUL_DISABLE_SSL_VERIFYSTATUS 234 | throw std::runtime_error("Ppconsul was built without support for CURLOPT_SSL_VERIFYSTATUS"); 235 | #else 236 | setopt(CURLOPT_SSL_VERIFYSTATUS, 1l); 237 | #endif 238 | } 239 | } 240 | 241 | CurlHttpClient::~CurlHttpClient() = default; 242 | 243 | CurlHttpClient::GetResponse CurlHttpClient::get(const std::string& path, const std::string& query, 244 | const http::RequestHeaders & headers) 245 | { 246 | GetResponse r; 247 | std::get<2>(r).reserve(Buffer_Size); 248 | 249 | setopt(CURLOPT_HEADERFUNCTION, &headerCallback); 250 | setopt(CURLOPT_CUSTOMREQUEST, nullptr); 251 | setopt(CURLOPT_URL, makeUrl(path, query).c_str()); 252 | setopt(CURLOPT_WRITEDATA, &std::get<2>(r)); 253 | setopt(CURLOPT_HEADERDATA, &r); 254 | setopt(CURLOPT_HTTPGET, 1l); 255 | setHeaders(headers); 256 | perform(); 257 | 258 | return r; 259 | } 260 | 261 | CurlHttpClient::PutResponse CurlHttpClient::put(const std::string& path, const std::string& query, 262 | const std::string& data, const http::RequestHeaders & headers) 263 | { 264 | ReadContext ctx(&data, 0u); 265 | 266 | std::pair r; 267 | r.second.reserve(Buffer_Size); 268 | 269 | setopt(CURLOPT_HEADERFUNCTION, &headerStatusCallback); 270 | setopt(CURLOPT_CUSTOMREQUEST, nullptr); 271 | setopt(CURLOPT_URL, makeUrl(path, query).c_str()); 272 | setopt(CURLOPT_WRITEDATA, &r.second); 273 | setopt(CURLOPT_HEADERDATA, &r.first); 274 | setopt(CURLOPT_UPLOAD, 1l); 275 | setopt(CURLOPT_PUT, 1l); 276 | setopt(CURLOPT_INFILESIZE_LARGE, static_cast(data.size())); 277 | setopt(CURLOPT_READDATA, &ctx); 278 | setHeaders(headers); 279 | perform(); 280 | 281 | return r; 282 | } 283 | 284 | CurlHttpClient::DelResponse CurlHttpClient::del(const std::string& path, const std::string& query, 285 | const http::RequestHeaders & headers) 286 | { 287 | std::pair r; 288 | r.second.reserve(Buffer_Size); 289 | 290 | setopt(CURLOPT_HEADERFUNCTION, &headerStatusCallback); 291 | setopt(CURLOPT_URL, makeUrl(path, query).c_str()); 292 | setopt(CURLOPT_WRITEDATA, &r.second); 293 | setopt(CURLOPT_HEADERDATA, &r.first); 294 | setopt(CURLOPT_HTTPGET, 1l); 295 | setopt(CURLOPT_CUSTOMREQUEST, "DELETE"); 296 | setHeaders(headers); 297 | perform(); 298 | 299 | return r; 300 | } 301 | 302 | template 303 | inline void CurlHttpClient::setopt(Opt opt, const T& t) 304 | { 305 | const auto err = curl_easy_setopt(handle(), opt, t); 306 | if (err) 307 | throwCurlError(err, m_errBuffer); 308 | } 309 | 310 | inline void CurlHttpClient::perform() 311 | { 312 | const auto err = curl_easy_perform(handle()); 313 | if (err) 314 | throwCurlError(err, m_errBuffer); 315 | } 316 | 317 | void CurlHttpClient::setHeaders(const http::RequestHeaders & headers) 318 | { 319 | m_headers = makeCurlHeaderList(headers); 320 | 321 | setopt(CURLOPT_HTTPHEADER, m_headers.get()); 322 | } 323 | }} 324 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ppconsul 2 | 3 | *Version 0.2* 4 | 5 | A C++ client library for [Consul](http://consul.io). Consul is a distributed tool for discovering and configuring services in your infrastructure. 6 | 7 | The goal of Ppconsul is to: 8 | * Fully cover version 1 of Consul [HTTP API](http://www.consul.io/docs/agent/http.html). Please check the current [implementation status](status.md). 9 | * Provide simple, modular and effective API based on C++11. 10 | * Support different platforms. At the moment, Linux, Windows and macOS platforms supported. 11 | * Cover all the code with automated tests. 12 | 13 | Note that this project is under development and doesn't promise a stable interface. 14 | 15 | Library tests are currently running against **Consul v1.11.1**. Library is known to work with Consul starting from version **0.4** (earlier versions might work as well but has never been tested) although some tests fail for older versions because of backward incompatible changes in Consul. 16 | 17 | The library is written in C++11 and requires a quite modern compiler. Currently it's compiled with: 18 | * macOS: Clang 11 (Xcode 11.3.1) 19 | * Ubuntu Linux: GCC 7.4 with stdlibc++ 20 | * Windows: n/a 21 | 22 | Oldest versions of compilers that should work (all with using C++11 standard) 23 | - Clang 5 24 | - GCC 4.8 25 | - Visual Studio 2013 26 | 27 | I try to support all modern compilers and platforms but I don't have resources to do extensive testing so from time to time something got broken on some platforms (mostly old GCC or Windows issues). Please create an issue if you discover a compilation error on your platform. 28 | 29 | The library depends on: 30 | 31 | * [git](https://git-scm.com/) (optional) to deduce correct .so version 32 | * [Boost](http://www.boost.org/) 1.55 or later. Ppconsul needs only headers with one exception: using of GCC 4.8 requires Boost.Regex library because [regular expressions are broken in GCC 4.8](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53631). 33 | * [libCURL](http://curl.haxx.se/libcurl/) to do HTTP/HTTPS. 34 | 35 | The library includes code of the following 3rd party libraries (check `ext` directory): 36 | 37 | * [json11](https://github.com/dropbox/json11) library to deal with JSON. 38 | * [libb64](http://libb64.sourceforge.net/) library for base64 decoding. 39 | 40 | For unit tests, the library uses [Catch2](https://github.com/catchorg/Catch2) framework. Many thanks to Phil Nash for this great product. 41 | 42 | ## Warm Up Examples 43 | 44 | ### Register, deregister and report the state of your service in Consul: 45 | 46 | ```cpp 47 | #include "ppconsul/agent.h" 48 | 49 | using ppconsul::Consul; 50 | using namespace ppconsul::agent; 51 | 52 | // Create a consul client that uses default local endpoint `http://127.0.0.1:8500` and default data center 53 | Consul consul; 54 | // We need the 'agent' endpoint for a service registration 55 | Agent agent(consul); 56 | 57 | // Register a service with associated HTTP check: 58 | agent.registerService( 59 | kw::name = "my-service", 60 | kw::port = 9876, 61 | kw::tags = {"tcp", "super_server"}, 62 | kw::check = HttpCheck{"http://localhost:80/", std::chrono::seconds(2)} 63 | ); 64 | 65 | ... 66 | 67 | // Unregister service 68 | agent.deregisterService("my-service"); 69 | 70 | ... 71 | 72 | // Register a service with TTL 73 | agent.registerService( 74 | kw::name = "my-service", 75 | kw::port = 9876, 76 | kw::id = "my-service-1", 77 | kw::check = TtlCheck{std::chrono::seconds(5)} 78 | ); 79 | 80 | // Report service is OK 81 | agent.servicePass("my-service-1"); 82 | 83 | // Report service is failed 84 | agent.serviceFail("my-service-1", "Disk is full"); 85 | ``` 86 | 87 | ### Determine raft leader (or lack thereof) and raft peers: 88 | 89 | ```cpp 90 | #include "ppconsul/status.h" 91 | 92 | using ppconsul::Consul; 93 | using namespace ppconsul::status; 94 | 95 | // Create a consul client that uses default local endpoint `http://127.0.0.1:8500` and default data center 96 | Consul consul; 97 | 98 | // We need the status endpoint 99 | Status status(consul); 100 | 101 | // Determine whether a leader has been elected 102 | bool isLeaderElected = status.isLeaderElected(); 103 | 104 | // Determine the actual raft leader 105 | auto leader = status.leader(); 106 | 107 | // Determine the raft peers 108 | auto peers = status.peers(); 109 | ``` 110 | 111 | ### Use Key-Value storage: 112 | 113 | ```cpp 114 | #include "ppconsul/kv.h" 115 | 116 | using ppconsul::Consul; 117 | using ppconsul::Consistency; 118 | using namespace ppconsul::kv; 119 | 120 | Consul consul; 121 | 122 | // We need the 'kv' endpoint 123 | Kv kv(consul); 124 | 125 | // Read the value of a key from the storage 126 | std::string something = kv.get("settings.something", "default-value"); 127 | 128 | // Read the value of a key from the storage with consistency mode specified 129 | something = kv.get("settings.something", "default-value", kw::consistency = Consistency::Consistent); 130 | 131 | // Erase a key from the storage 132 | kv.erase("settings.something-else"); 133 | 134 | // Set the value of a key 135 | kv.set("settings.something", "new-value"); 136 | ``` 137 | 138 | ### Blocking query to Key-Value storage: 139 | 140 | ```cpp 141 | // Get key+value+metadata item 142 | KeyValue item = kv.item("status.last-event-id"); 143 | 144 | // Wait for the item change for no more than 1 minute: 145 | item = kv.item("status.last-event-id", kw::block_for = {std::chrono::minutes(1), item.modifyIndex}); 146 | 147 | // If key exists, print it: 148 | if (item) 149 | std::cout << item.key << "=" << item.value << "\n"; 150 | ``` 151 | 152 | ### Abort all [blocking] queries: 153 | 154 | ```cpp 155 | Consul consul(kw::enable_stop = true); // Must be enabled at construction time 156 | Kv kv(consul); 157 | 158 | // Issue blocking queries, similarly to example above, on background threads etc. 159 | 160 | // Stop all pending requests, e.g. at shutdown. No further requests can be done after this call. 161 | consul.stop(); 162 | ``` 163 | 164 | Call to `Consul::stop()` is irreversible: once it's done the `Consul` object is switched to the stopped state forever. This whole feature purpose is to gracefully abort ongoing blocking queries on application/component shutdown. 165 | 166 | ### Configure connect and request timeouts 167 | 168 | By default, connect timeout is set to 5 seconds and request timeout is not set (so it is unlimited). If needed you can override default values as following: 169 | 170 | ```cpp 171 | #include "ppconsul/consul.h" 172 | 173 | using namespace ppconsul; 174 | 175 | Consul consul("https://localhost:8080", 176 | kw::connect_timeout = std::chrono::milliseconds{15000} 177 | kw::request_timeout = std::chrono::milliseconds{5000}); 178 | 179 | // Use consul ... 180 | ``` 181 | 182 | If you're using blocking queries then make sure that request timeout is longer than block interval, otherwise request will fail with timeout error. 183 | 184 | ### Connect to Consul via HTTPS (TLS/SSL, whatever you call it): 185 | 186 | ```cpp 187 | #include "ppconsul/consul.h" 188 | 189 | using namespace ppconsul; 190 | 191 | Consul consul("https://localhost:8080", 192 | kw::tls::cert="path/to/cert", 193 | kw::tls::key="path/to/private/key", 194 | kw::tls::ca_info="path/to/ca/cert"); 195 | 196 | // Use consul ... 197 | ``` 198 | 199 | ## Multi-Threaded Usage of Ppconsul 200 | 201 | Each Consul object has a pool of HTTP(S) clients to perform network requests. It is safe to call any endpoint (e.g. `Kv`, `Agent` etc) object or Consul object from multiple threads in the same time. 202 | 203 | Call to `Consul::stop()` method stops all ongoing requests on that particular `Consul` object. 204 | 205 | ## Multi-Threaded Usage of Ppconsul and libCURL initialization 206 | 207 | libCURL requires that global initialization function [`curl_global_init`](https://curl.se/libcurl/c/curl_global_init.html) is called before any oither libCURL function is called and before any additional thread is started. 208 | Ppconsul calls `curl_global_init` for you at the moment when `makeDefaultHttpClientFactory()` is called for the first time which is usually done when first `Consul` object is created. If this is too late then you need to call to `curl_global_init` and `curl_global_cleanup` at the right moment yourself, similar to this example: 209 | 210 | ```cpp 211 | int main(int argc, char *argv[]) 212 | { 213 | curl_global_init(CURL_GLOBAL_DEFAULT | CURL_GLOBAL_SSL); 214 | 215 | // Existing code that uses Ppconsul and starts extra threads... 216 | 217 | curl_global_cleanup(); 218 | 219 | return 0; 220 | } 221 | ``` 222 | 223 | `CURL_GLOBAL_SSL` flag is only needed if your're using HTTPS (TLS/SSL). 224 | 225 | If your application needs to exclusively control libCURL initialization then you m ay want to skip libCURL initialization in Ppconsul's default HttpClientFactory. To do this, create default HttpClientFactory explicitly via `makeDefaultHttpClientFactory(false)` and pass it to `Consul`: 226 | 227 | ```cpp 228 | Consul consul(makeDefaultHttpClientFactory(false), ...); 229 | ``` 230 | 231 | ## Custom `http::HttpClient` 232 | 233 | If needed, user can implement `http::HttpClient` interface and pass custom `HttpClientFactory` to `Consul`'s constructor. 234 | 235 | ## Documentation 236 | 237 | TBD 238 | 239 | ## How To Build 240 | 241 | You need C++11 compatible compiler (see above for the list of supported compilers) and [CMake](http://www.cmake.org/) 3.1 or above. 242 | 243 | ### TLDR 244 | 245 | ```bash 246 | # Install dependencies 247 | conan install . 248 | 249 | # Make workspace directory 250 | mkdir workspace 251 | cd workspace 252 | 253 | # Configure: 254 | cmake .. 255 | 256 | #Build 257 | cmake --build . --config Release 258 | 259 | # Install 260 | cmake --build . --config Release --target install 261 | ``` 262 | 263 | ### Get dependencies 264 | 265 | If you use [Conan](https://conan.io/) then simply run `conan install .` to install dependencies. 266 | 267 | Otherwise: 268 | 269 | * Install [git](https://git-scm.com/) (any version should be fine) 270 | * Install [Boost](http://www.boost.org/) 1.55 or later. You need compiled Boost.Regex library if you use GCC 4.8, otherwise you need headers only. 271 | * Install [libCURL](http://curl.haxx.se/libcurl/) (any version should be fine). 272 | 273 | ### Build 274 | 275 | Configure project: 276 | 277 | ```bash 278 | mkdir workspace 279 | cd workspace 280 | cmake .. 281 | ``` 282 | 283 | You may want to set the following CMake variables on the command line: 284 | 285 | To change where CMake looks for Boost, pass `-DBOOST_ROOT=` parameter to CMake or set `BOOST_ROOT` environment variable. 286 | 287 | To change where CMake looks for libCURL, pass `-DCURL_ROOT=` parameter to CMake or set `CURL_ROOT` environment variable. 288 | 289 | To change default install location, pass `-DCMAKE_INSTALL_PREFIX=` parameter. 290 | 291 | To build Ppconsul as static library, pass `-DBUILD_STATIC_LIB=ON` parameter. 292 | Note that in this case you have to link with json11 static library as well (json11 library is build as part of Ppconsul build.) 293 | 294 | **Note that on Windows Ppconsul can only be built as static library (that's default mode on Windows), see issue [Allow to build Ppconsul as dynamic library on Windows](https://github.com/oliora/ppconsul/issues/25)**. 295 | 296 | *Note about -G option of CMake to choose you favourite IDE to generate project files for.* 297 | 298 | Build: 299 | 300 | ```bash 301 | cmake --build . --config Release 302 | ``` 303 | 304 | If Makefile generator was used then you can also do: 305 | 306 | ```bash 307 | make 308 | ``` 309 | 310 | ## How to Install 311 | 312 | Build it first as described above then run 313 | 314 | ```bash 315 | cmake --build . --config Release --target install 316 | ``` 317 | 318 | If Makefile generator was used then you can also do: 319 | 320 | ```bash 321 | make install 322 | ``` 323 | 324 | ## How To Run Tests 325 | Install [Consul](http://consul.io) 0.4 or newer. *I recommend 0.7 or newer since it's easier to run it in development mode.* 326 | 327 | ### Run Consul 328 | 329 | For Consul 0.9 and above: 330 | 331 | ```bash 332 | consul agent -dev -datacenter=ppconsul_test -enable-script-checks=true 333 | ``` 334 | 335 | For Consul 0.7 and 0.8: 336 | 337 | ```bash 338 | consul agent -dev -datacenter=ppconsul_test 339 | ``` 340 | 341 | For earlier version of Consul follow its documentation on how to run it with `ppconsul_test` datacenter. 342 | 343 | ### Run Tests 344 | 345 | ```bash 346 | ctest -C Release 347 | ``` 348 | 349 | If Makefile generator was used then you can also do: 350 | 351 | ```bash 352 | make test 353 | ``` 354 | 355 | There are the following environment variable to configure tests: 356 | 357 | |Name|Default Value|Description| 358 | |----|-------------|-----------| 359 | |`PPCONSUL_TEST_ADDR`|"127.0.0.1:8500"|The Consul network address| 360 | |`PPCONSUL_TEST_DC`|"ppconsul_test"|The Consul datacenter| 361 | |`PPCONSUL_TEST_LEADER_ADDR`|"127.0.0.1:8300"|The Consul raft leader address| 362 | 363 | **Never set `PPCONSUL_TEST_DC` into a datacenter that you can't throw away because Ppconsul tests will screw it up in many different ways.** 364 | 365 | ### Known Problems 366 | 367 | Sometimes catalog tests failed on assertion `REQUIRE(index1 == resp1.headers().index());`. In this case, just rerun the tests. 368 | The reason for the failure is Consul's internal idempotent write which cause a spurious wakeup of waiting blocking query. Check the critical note under the blocking queries documentation at https://www.consul.io/docs/agent/http.html. 369 | 370 | ## How to Use Ppconsul in Your Project 371 | 372 | Build and install it first as described above. 373 | 374 | When installed, the library can be simply used in any CMake-based project as following: 375 | 376 | ``` 377 | find_package(ppconsul) 378 | add_executable( ...) 379 | target_link_libraries( ppconsul) 380 | ``` 381 | 382 | As an alternative you can clone ppconsul into your project as submodule: 383 | 384 | ``` 385 | git submodule add https://github.com/oliora/ppconsul.git 386 | ``` 387 | 388 | And then include it into your CMake-based project as subdirectory: 389 | 390 | ``` 391 | set(BUILD_TESTS OFF) 392 | set(BUILD_STATIC_LIB ON) 393 | add_subdirectory(ppconsul) 394 | ... 395 | target_link_libraries( ppconsul) 396 | ``` 397 | 398 | 399 | ## Found a bug? Got a feature request? Need help with Ppconsul? 400 | Use [issue tracker](https://github.com/oliora/ppconsul/issues) or/and drop an email to [oliora](https://github.com/oliora). 401 | 402 | ## Contribute 403 | First of all, welcome on board! 404 | 405 | To contribute, please fork this repo, make your changes and create a pull request. 406 | 407 | ## License 408 | The library released under [Boost Software License v1.0](http://www.boost.org/LICENSE_1_0.txt). 409 | --------------------------------------------------------------------------------