diff --git a/src/cloud/azure/custom/api.pm b/src/cloud/azure/custom/api.pm index ac3fa02ffc..dd09cf79a7 100644 --- a/src/cloud/azure/custom/api.pm +++ b/src/cloud/azure/custom/api.pm @@ -811,6 +811,38 @@ sub azure_list_sqldatabases { return $full_response; } +sub azure_get_resource_graph_set_url { + my ($self, %options) = @_; + + my $url = $self->{management_endpoint} . '/providers/Microsoft.ResourceGraph/resources?api-version=' . $self->{api_version}; + + return $url; +} + +sub azure_get_resource_graph { + my ($self, %options) = @_; + + my $full_url = $self->azure_get_resource_graph_set_url(); + + my $body = { query => $options{query} }; + $body->{subscriptions} = [ $self->{subscription} ] if (defined($self->{subscription}) && $self->{subscription} ne ''); + + my $encoded_body; + eval { + $encoded_body = JSON::XS->new->utf8->encode($body); + }; + + my $response = $self->request_api( + method => 'POST', + full_url => $full_url, + hostname => '', + query_form_post => $encoded_body, + header => ['Content-Type: application/json'] + ); + + return $response; +} + sub azure_get_log_analytics_set_url { my ($self, %options) = @_; diff --git a/src/cloud/azure/custom/graphexplorer/custom/api.pm b/src/cloud/azure/custom/graphexplorer/custom/api.pm new file mode 100644 index 0000000000..451c9631a3 --- /dev/null +++ b/src/cloud/azure/custom/graphexplorer/custom/api.pm @@ -0,0 +1,74 @@ +# +# Copyright 2024 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package cloud::azure::management::graphexplorer::custom::api; + +use strict; +use warnings; +use base qw(cloud::azure::custom::api); + +# Override check_options to make --subscription optional. +# Azure Resource Graph can query at tenant scope without a subscription. +sub check_options { + my ($self, %options) = @_; + + $self->{timeout} = (defined($self->{option_results}->{timeout})) ? $self->{option_results}->{timeout} : 10; + $self->{subscription} = (defined($self->{option_results}->{subscription})) ? $self->{option_results}->{subscription} : undef; + $self->{tenant} = (defined($self->{option_results}->{tenant})) ? $self->{option_results}->{tenant} : undef; + $self->{client_id} = (defined($self->{option_results}->{client_id})) ? $self->{option_results}->{client_id} : undef; + $self->{client_secret} = (defined($self->{option_results}->{client_secret})) ? $self->{option_results}->{client_secret} : undef; + $self->{login_endpoint} = (defined($self->{option_results}->{login_endpoint})) ? $self->{option_results}->{login_endpoint} : 'https://login.microsoftonline.com'; + $self->{management_endpoint} = (defined($self->{option_results}->{management_endpoint})) ? $self->{option_results}->{management_endpoint} : 'https://management.azure.com'; + $self->{api_version} = (defined($self->{option_results}->{api_version})) ? $self->{option_results}->{api_version} : undef; + + # --subscription is optional for Resource Graph (tenant-scope queries are allowed) + if (!defined($self->{tenant}) || $self->{tenant} eq '') { + $self->{output}->add_option_msg(short_msg => "Need to specify --tenant option."); + $self->{output}->option_exit(); + } + if (!defined($self->{client_id}) || $self->{client_id} eq '') { + $self->{output}->add_option_msg(short_msg => "Need to specify --client-id option."); + $self->{output}->option_exit(); + } + if (!defined($self->{client_secret}) || $self->{client_secret} eq '') { + $self->{output}->add_option_msg(short_msg => "Need to specify --client-secret option."); + $self->{output}->option_exit(); + } + if (!defined($self->{api_version}) || $self->{api_version} eq '') { + $self->{output}->add_option_msg(short_msg => "Need to specify --api-version option."); + $self->{output}->option_exit(); + } + + $self->{cache}->check_options(option_results => $self->{option_results}); + + return 0; +} + +1; + +__END__ + +=head1 DESCRIPTION + +B. Azure Resource Graph custom API mode. +Extends cloud::azure::custom::api with --subscription made optional, +allowing tenant-scoped Resource Graph queries. + +=cut diff --git a/src/cloud/azure/custom/graphexplorer/mode/query.pm b/src/cloud/azure/custom/graphexplorer/mode/query.pm new file mode 100644 index 0000000000..3771077c96 --- /dev/null +++ b/src/cloud/azure/custom/graphexplorer/mode/query.pm @@ -0,0 +1,164 @@ +# +# Copyright 2024 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package cloud::azure::management::graphexplorer::mode::query; + +use base qw(centreon::plugins::templates::counter); + +use strict; +use warnings; + +sub custom_match_output { + my ($self, %options) = @_; + + my $msg; + my $message; + + if (defined($self->{instance_mode}->{option_results}->{custom_output}) && $self->{instance_mode}->{option_results}->{custom_output} ne '') { + eval { + local $SIG{__WARN__} = sub { $message = $_[0]; }; + local $SIG{__DIE__} = sub { $message = $_[0]; }; + $msg = sprintf($self->{instance_mode}->{option_results}->{custom_output}, $self->{result_values}->{match}); + }; + } else { + $msg = sprintf("Resource Graph query returned '%d' result(s)", $self->{result_values}->{match}); + } + + if (defined($message)) { + $self->{output}->output_add(long_msg => 'printf substitution issue: ' . $message); + } + return $msg; +} + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'global', type => 0 } + ]; + + $self->{maps_counters}->{global} = [ + { label => 'match', nlabel => 'resourcegraph.query.match.count', set => { + key_values => [ { name => 'match' } ], + closure_custom_output => $self->can('custom_match_output'), + perfdatas => [ + { template => '%s', min => 0 } + ] + } + } + ]; +} + +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 => { + 'query:s' => { name => 'query' }, + 'custom-output:s' => { name => 'custom_output' }, + }); + + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::check_options(%options); + + if (!defined($self->{option_results}->{query}) || $self->{option_results}->{query} eq '') { + $self->{output}->add_option_msg(short_msg => "Need to specify --query option."); + $self->{output}->option_exit(); + } +} + +sub manage_selection { + my ($self, %options) = @_; + + my $response = $options{custom}->azure_get_resource_graph( + query => $self->{option_results}->{query} + ); + + my $count = 0; + if (defined($response->{data}) && ref($response->{data}) eq 'ARRAY') { + $count = scalar(@{$response->{data}}); + } elsif (defined($response->{count})) { + $count = $response->{count}; + } + + if (defined($response->{resultTruncated}) && $response->{resultTruncated} eq 'true') { + $self->{output}->output_add(long_msg => + "Warning: result set is truncated (totalRecords: " . $response->{totalRecords} . "). " + . "Use 'top' in your KQL query to reduce the result set." + ); + } + + if (defined($response->{data}) && ref($response->{data}) eq 'ARRAY') { + foreach my $row (@{$response->{data}}) { + $self->{output}->output_add(long_msg => join(', ', map { $_ . '=' . (defined($row->{$_}) ? $row->{$_} : '') } keys %{$row})); + } + } + + $self->{global} = { match => $count }; +} + +1; + +__END__ + +=head1 MODE + +Run a KQL query against Azure Resource Graph and count the number of results. + +Sample command: +perl centreon_plugins.pl --plugin=cloud::azure::management::graphexplorer::plugin \ + --custommode api --mode query \ + --subscription=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX \ + --tenant=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX \ + --client-id=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX \ + --client-secret=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \ + --query "Resources | where type =~ 'microsoft.compute/virtualmachines' | where properties.powerState.code == 'PowerState/running'" \ + --custom-output='Running VMs: %d' \ + --warning-match=10 --critical-match=20 +OK: Running VMs: 5 | 'resourcegraph.query.match.count'=5;10;20;0; + +=over 8 + +=item B<--query> + +KQL query to run against Azure Resource Graph (required). +See https://docs.microsoft.com/en-us/azure/governance/resource-graph/concepts/query-language + +=item B<--custom-output> + +Custom output string in printf format. Use %d as placeholder for the result count. +Example: 'Running VMs: %d' + +=item B<--warning-match> + +Warning threshold on the number of results returned by the query. + +=item B<--critical-match> + +Critical threshold on the number of results returned by the query. + +=back + +=cut diff --git a/src/cloud/azure/custom/graphexplorer/plugin.pm b/src/cloud/azure/custom/graphexplorer/plugin.pm new file mode 100644 index 0000000000..bf66e59ea5 --- /dev/null +++ b/src/cloud/azure/custom/graphexplorer/plugin.pm @@ -0,0 +1,59 @@ +# +# Copyright 2024 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package cloud::azure::management::graphexplorer::plugin; + +use strict; +use warnings; +use base qw(centreon::plugins::script_custom); + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $self->{version} = '0.1'; + %{$self->{modes}} = ( + 'query' => 'cloud::azure::management::graphexplorer::mode::query', + ); + + $self->{custom_modes}{api} = 'cloud::azure::management::graphexplorer::custom::api'; + return $self; +} + +sub init { + my ($self, %options) = @_; + + $self->{options}->add_options(arguments => { + 'api-version:s' => { name => 'api_version', default => '2024-04-01' }, + }); + + $self->SUPER::init(%options); +} + +1; + +__END__ + +=head1 PLUGIN DESCRIPTION + +Check Microsoft Azure Resource Graph using KQL queries. + +=cut