diff --git a/src/apps/hashicorp/vault/restapi/mode/health.pm b/src/apps/hashicorp/vault/restapi/mode/health.pm index 2e889c8df..faf3adbbd 100644 --- a/src/apps/hashicorp/vault/restapi/mode/health.pm +++ b/src/apps/hashicorp/vault/restapi/mode/health.pm @@ -47,6 +47,13 @@ sub set_counters { closure_custom_perfdata => sub { return 0; }, closure_custom_threshold_check => \&catalog_status_threshold_ng } + }, + { label => 'standby-status', type => 2, set => { + key_values => [ { name => 'standby' } ], + output_template => "standby status : %s", + closure_custom_perfdata => sub { return 0; }, + closure_custom_threshold_check => \&catalog_status_threshold_ng + } } ]; } @@ -57,12 +64,32 @@ sub custom_prefix_output { return 'Server ' . $options{instance_value}->{cluster_name} . ' '; } +# List of parameteres added during the API call +our @code_parameters = ( + { 'code' => 'perfstandbyok', 'type' => 'bool' }, + { 'code' => 'activecode', 'type' => 'status' }, + { 'code' => 'drsecondarycode', 'type' => 'status' }, + { 'code' => 'haunhealthycode', 'type' => 'status' }, + { 'code' => 'performancestandbycode', 'type' => 'status' }, + { 'code' => 'removedcode', 'type' => 'status' }, + # By default API will return error codes if sealed, uninit or standby + { 'code' => 'standbyok', 'type' => 'bool', default => 'true' }, + { 'code' => 'sealedcode', 'type' => 'status', default => '200' }, + { 'code' => 'uninitcode', 'type' => 'status', default => '200' }, + { 'code' => 'standbycode', 'type' => 'status', default => '200' }, +); + sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); bless $self, $class; - $options{options}->add_options(arguments => {}); + my %arguments; + + $arguments{$_->{'code'}.':s'} = { name => $_->{'code'}, default => $_->{'default'} // '', } + foreach (@code_parameters); + + $options{options}->add_options(arguments => \%arguments ); return $self; } @@ -71,17 +98,49 @@ sub set_options { my ($self, %options) = @_; } +sub check_options { + my ($self, %options) = @_; + $self->SUPER::check_options(%options); + + foreach my $param (@code_parameters) { + my $value = lc $self->{option_results}->{$param->{'code'}}; + next if $value eq ''; + + my $valid = 0; + if ($param->{'type'} eq 'status') { + $valid = $value =~ /^\d{1,3}$/; + } elsif ($param->{'type'} eq 'bool') { + $valid = $value eq 'true' || $value eq 'false'; + + $self->{option_results}->{$param->{code}} = lc $value; + } + + unless ($valid) { + $self->{output}->add_option_msg(short_msg => "Invalid value for ".$param->{'code'}."."); + $self->{output}->option_exit(); + } + } +} + sub manage_selection { my ($self, %options) = @_; - my $code_param = '?sealedcode=200&uninitcode=200'; # By default API will return error codes if sealed or uninit - my $result = $options{custom}->request_api(url_path => 'health' . $code_param); + my @code_arr; + + foreach my $param (@code_parameters) { + next if $self->{option_results}->{$param->{'code'}} eq ''; + + push @code_arr, $param->{'code'} .'='. $self->{option_results}->{$param->{'code'}}; + } + + my $result = $options{custom}->request_api(url_path => 'health' . (@code_arr ? '?'.join('&', @code_arr) : '')); my $cluster_name = defined($result->{cluster_name}) ? $result->{cluster_name} : $self->{option_results}->{hostname}; $self->{vault_cluster}->{$cluster_name} = { cluster_name => $cluster_name, sealed => $result->{sealed} ? 'sealed' : 'unsealed', init => $result->{initialized} ? 'initialized' : 'not initialized', + standby => $result->{standby} ? 'true' : 'false', }; } @@ -91,16 +150,22 @@ __END__ =head1 MODE -Check Hashicorp Vault Health status. +Check HashiCorp Vault Health status. Example: perl centreon_plugins.pl --plugin=apps::hashicorp::vault::restapi::plugin --mode=health --hostname=10.0.0.1 --vault-token='s.aBCD123DEF456GHI789JKL012' --verbose -More information on'https://www.vaultproject.io/api-docs/system/health'. +More information on'https://developer.hashicorp.com/vault/api-docs/system/health'. =over 8 +=item B<--standbyok --perfstandbyok --activecode --standbycode --drsecondarycode --haunhealthycode --performancestandbycode --removedcode --sealedcode --uninitcode> + +Arguments to pass to the health API call, default are --sealedcode=200 and --uninitcode=200. + +More information on'https://developer.hashicorp.com/vault/api-docs/system/health#parameters.' + =item B<--warning-seal-status> Set warning threshold for seal status (default: none). @@ -117,6 +182,14 @@ Set warning threshold for initialization status (default: none). Set critical threshold for initialization status (default: '%{init} ne "initialized"'). +=item B<--warning-standby-status> + +Set warning threshold for standby status (default: none). + +=item B<--critical-standby-status> + +Set critical threshold for standby status (default: none). + =back =cut diff --git a/tests/apps/hashicorp/vault/restapi/hashicorp-health.mockoon.json b/tests/apps/hashicorp/vault/restapi/hashicorp-health.mockoon.json new file mode 100644 index 000000000..a2e402c3b --- /dev/null +++ b/tests/apps/hashicorp/vault/restapi/hashicorp-health.mockoon.json @@ -0,0 +1,312 @@ +{ + "uuid": "83005144-3d67-4049-be7e-e57a474c9ee2", + "lastMigration": 33, + "name": "Hshicorp", + "endpointPrefix": "", + "latency": 0, + "port": 3004, + "hostname": "", + "folders": [], + "routes": [ + { + "uuid": "60154517-befe-43d0-a9cd-5f5d1afffde9", + "type": "http", + "documentation": "", + "method": "get", + "endpoint": "v1/sys/health", + "responses": [ + { + "uuid": "a97e1d68-09ff-4940-89b0-982bb9f78e83", + "body": "{\n \"initialized\": true,\n \"sealed\": false,\n \"standby\": false,\n \"performance_standby\": false,\n \"replication_dr_mode\": \"disabled\",\n \"replication_performance_mode\": \"disabled\",\n \"server_time_utc\": 1720000000,\n \"version\": \"1.0.3\",\n \"cluster_name\": \"test-cluster-master\",\n \"cluster_id\": \"abab-0101-bcbd-12345abcde\"\n}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "request_number", + "modifier": "", + "value": "1", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "417f537a-6375-44ad-961f-cbe9f599b624", + "body": "{\n \"initialized\": false,\n \"sealed\": false,\n \"standby\": false,\n \"performance_standby\": false,\n \"replication_dr_mode\": \"disabled\",\n \"replication_performance_mode\": \"disabled\",\n \"server_time_utc\": 1720000000,\n \"version\": \"1.0.3\",\n \"cluster_name\": \"test-cluster-uninit\",\n \"cluster_id\": \"abab-0101-bcbd-12345abcde\"\n}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "request_number", + "modifier": "", + "value": "2", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "66a60ca3-f290-4f89-9ffe-e276994c0ae9", + "body": "{\n \"initialized\": true,\n \"sealed\": false,\n \"standby\": true,\n \"performance_standby\": false,\n \"replication_dr_mode\": \"disabled\",\n \"replication_performance_mode\": \"disabled\",\n \"server_time_utc\": 1720000000,\n \"version\": \"1.0.3\",\n \"cluster_name\": \"test-cluster-standby\",\n \"cluster_id\": \"abab-0101-bcbd-12345abcde\"\n}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "request_number", + "modifier": "", + "value": "3", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "f5cb8784-dc3f-4e4d-9eda-f5d0963febe0", + "body": "{\n \"initialized\": true,\n \"sealed\": true,\n \"standby\": false,\n \"performance_standby\": false,\n \"replication_dr_mode\": \"disabled\",\n \"replication_performance_mode\": \"disabled\",\n \"server_time_utc\": 1720000000,\n \"version\": \"1.0.3\",\n \"cluster_name\": \"test-cluster-sealed\",\n \"cluster_id\": \"abab-0101-bcbd-12345abcde\"\n}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "request_number", + "modifier": "", + "value": "4", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "cd88bf44-148c-45d0-95a0-d7a635030bb7", + "body": "{\n \"initialized\": false,\n \"sealed\": false,\n \"standby\": true,\n \"performance_standby\": false,\n \"replication_dr_mode\": \"disabled\",\n \"replication_performance_mode\": \"disabled\",\n \"server_time_utc\": 1720000000,\n \"version\": \"1.0.3\",\n \"cluster_name\": \"test-cluster-uninitstandby\",\n \"cluster_id\": \"abab-0101-bcbd-12345abcde\"\n}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "request_number", + "modifier": "", + "value": "5", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "0199facc-b9f6-4565-b73a-cddd52c37f75", + "body": "{\n \"initialized\": true,\n \"sealed\": false,\n \"standby\": true,\n \"performance_standby\": false,\n \"replication_dr_mode\": \"disabled\",\n \"replication_performance_mode\": \"disabled\",\n \"server_time_utc\": 1720000000,\n \"version\": \"1.0.3\",\n \"cluster_name\": \"test-cluster-standby-508\",\n \"cluster_id\": \"abab-0101-bcbd-12345abcde\"\n}", + "latency": 0, + "statusCode": 508, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "request_number", + "modifier": "", + "value": "6", + "invert": false, + "operator": "equals" + }, + { + "target": "query", + "modifier": "standbycode", + "value": "508", + "invert": false, + "operator": "equals" + }, + { + "target": "query", + "modifier": "standbyok", + "value": "true", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "AND", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "8f8d93f7-d426-4cbc-b975-7e9d4e970bd8", + "body": "{\n \"initialized\": true,\n \"sealed\": false,\n \"standby\": false,\n \"performance_standby\": true,\n \"replication_dr_mode\": \"disabled\",\n \"replication_performance_mode\": \"disabled\",\n \"server_time_utc\": 1720000000,\n \"version\": \"1.0.3\",\n \"cluster_name\": \"test-cluster-perfstandby-524,\n \"cluster_id\": \"abab-0101-bcbd-12345abcde\"\n}", + "latency": 0, + "statusCode": 524, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "request_number", + "modifier": "", + "value": "7", + "invert": false, + "operator": "equals" + }, + { + "target": "header", + "modifier": "performancestandbycode", + "value": "524", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "aca7de42-6586-4f3d-83ba-dc6eebdd014c", + "body": "{\n \"initialized\": true,\n \"sealed\": false,\n \"standby\": true,\n \"performance_standby\": false,\n \"replication_dr_mode\": \"disabled\",\n \"replication_performance_mode\": \"disabled\",\n \"server_time_utc\": 1720000000,\n \"version\": \"1.0.3\",\n \"cluster_name\": \"test-cluster-standby2\",\n \"cluster_id\": \"abab-0101-bcbd-12345abcde\"\n}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "request_number", + "modifier": "", + "value": "8", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + } + ], + "rootChildren": [ + { + "type": "route", + "uuid": "60154517-befe-43d0-a9cd-5f5d1afffde9" + } + ], + "proxyMode": false, + "proxyHost": "", + "proxyRemovePrefix": false, + "tlsOptions": { + "enabled": false, + "type": "CERT", + "pfxPath": "", + "certPath": "", + "keyPath": "", + "caPath": "", + "passphrase": "" + }, + "cors": true, + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "Content-Type, Origin, Accept, Authorization, Content-Length, X-Requested-With" + } + ], + "proxyReqHeaders": [ + { + "key": "", + "value": "" + } + ], + "proxyResHeaders": [ + { + "key": "", + "value": "" + } + ], + "data": [], + "callbacks": [] +} \ No newline at end of file diff --git a/tests/apps/hashicorp/vault/restapi/hashicorp-health.robot b/tests/apps/hashicorp/vault/restapi/hashicorp-health.robot new file mode 100644 index 000000000..f5e6ca2bd --- /dev/null +++ b/tests/apps/hashicorp/vault/restapi/hashicorp-health.robot @@ -0,0 +1,40 @@ +*** Settings *** +Documentation HashiCorp Vault REST API Health Check + +Resource ${CURDIR}${/}..${/}..${/}..${/}..${/}resources/import.resource + +Suite Setup Start Mockoon ${MOCKOON_JSON} +Suite Teardown Stop Mockoon +Test Timeout 120s + + +*** Variables *** +${MOCKOON_JSON} ${CURDIR}${/}hashicorp-health.mockoon.json +${HOSTNAME} 127.0.0.1 +${APIPORT} 3000 +${CMD} ${CENTREON_PLUGINS} +... --plugin=apps::hashicorp::vault::restapi::plugin +... --mode health +... --hostname=${HOSTNAME} +... --vault-token=xx-xxx +... --proto=http +... --port=${APIPORT} + +*** Test Cases *** +List Edges ${tc} + [Tags] apps hashicorp vault restapi mockoon + ${command} Catenate + ... ${CMD} + ... ${extra_options} + + Ctn Run Command And Check Result As Strings ${command} ${expected_result} + + Examples: tc extra_options expected_result -- + ... 1 ${EMPTY} OK: Server test-cluster-master seal status : unsealed, init status : initialized, standby status : false + ... 2 --warning-init-status='\\%\{init\} ne "initialized"' --critical-init-status='' WARNING: Server test-cluster-uninit init status : not initialized + ... 3 ${EMPTY} OK: Server test-cluster-standby seal status : unsealed, init status : initialized, standby status : true + ... 4 --warning-seal-status='\\%\{init\} ne "sealed"' --critical-seal-status='' WARNING: Server test-cluster-sealed seal status : sealed + ... 5 ${EMPTY} CRITICAL: Server test-cluster-uninitstandby init status : not initialized + ... 6 --standbycode=508 UNKNOWN: 508 Loop Detected + ... 7 --performancestandbycode=524 UNKNOWN: 524 unknown + ... 8 --critical-standby-status='\\%\{standby\} ne "false"' CRITICAL: Server test-cluster-standby2 standby status : true