bin/check-s3-object.rb
#! /usr/bin/env ruby
#
# check-s3-object
#
# DESCRIPTION:
# This plugin checks if a file exists in a bucket and/or is not too old.
#
# OUTPUT:
# plain-text
#
# PLATFORMS:
# Linux
#
# DEPENDENCIES:
# gem: aws-sdk
# gem: sensu-plugin
#
# USAGE:
# ./check-s3-object.rb --bucket-name mybucket --aws-region eu-west-1 --use-iam --key-name "path/to/myfile.txt"
# ./check-s3-object.rb --bucket-name mybucket --aws-region eu-west-1 --use-iam --key-name "path/to/myfile.txt" --warning 90000 --critical 126000
# ./check-s3-object.rb --bucket-name mybucket --aws-region eu-west-1 --use-iam --key-name "path/to/myfile.txt" --warning 90000 --critical 126000 --ok-zero-size
#
# NOTES:
#
# LICENSE:
# Copyright (c) 2015, Olivier Bazoud, olivier.bazoud@gmail.com
# Released under the same terms as Sensu (the MIT license); see LICENSE
# for details.
#
require 'sensu-plugin/check/cli'
require 'aws-sdk'
class CheckS3Object < Sensu::Plugin::Check::CLI
option :aws_access_key,
short: '-a AWS_ACCESS_KEY',
long: '--aws-access-key AWS_ACCESS_KEY',
description: "AWS Access Key. Either set ENV['AWS_ACCESS_KEY'] or provide it as an option",
default: ENV['AWS_ACCESS_KEY']
option :aws_secret_access_key,
short: '-k AWS_SECRET_KEY',
long: '--aws-secret-access-key AWS_SECRET_KEY',
description: "AWS Secret Access Key. Either set ENV['AWS_SECRET_KEY'] or provide it as an option",
default: ENV['AWS_SECRET_KEY']
option :aws_region,
short: '-r AWS_REGION',
long: '--aws-region REGION',
description: 'AWS Region (defaults to us-east-1).',
default: 'us-east-1'
option :use_iam_role,
short: '-u',
long: '--use-iam',
description: 'Use IAM role authenticiation. Instance must have IAM role assigned for this to work'
option :bucket_name,
short: '-b BUCKET_NAME',
long: '--bucket-name',
description: 'The name of the S3 bucket where object lives',
required: true
option :key_name,
short: '-n KEY_NAME',
long: '--key-name',
description: 'The name of key in the bucket'
option :key_prefix,
short: '-p KEY_PREFIX',
long: '--key-prefix',
description: 'Prefix key to search on the bucket'
option :warning_age,
description: 'Warn if mtime greater than provided age in seconds',
short: '-w SECONDS',
long: '--warning SECONDS'
option :critical_age,
description: 'Critical if mtime greater than provided age in seconds',
short: '-c SECONDS',
long: '--critical SECONDS'
option :ok_zero_size,
description: 'OK if file has zero size',
short: '-z',
long: '--ok-zero-size',
boolean: true,
default: false
option :warning_size,
description: 'Warning threshold for size',
long: '--warning-size COUNT'
option :critical_size,
description: 'Critical threshold for size',
long: '--critical-size COUNT'
option :compare_size,
description: 'Comparision operator for threshold: equal, not, greater, less',
short: '-o OPERATION',
long: '--operator-size OPERATION',
default: 'equal'
option :no_crit_on_multiple_objects,
description: 'If this flag is set, sort all matching objects by last_modified date and check against the newest. By default, this check will return a CRITICAL result if multiple matching objects are found.',
short: '-m',
long: '--ok-on-multiple-objects',
boolean: true
def aws_config
{ access_key_id: config[:aws_access_key],
secret_access_key: config[:aws_secret_access_key],
region: config[:aws_region] }
end
def operator
op = lambda do |type, a, b|
case type
when 'age'
a > b
when 'size'
if config[:compare_size] == 'greater'
a > b
elsif config[:compare_size] == 'less'
a < b
elsif config[:compare_size] == 'not'
a != b
end
else
a == b
end
end
op
end
def run_check(type, level, value, element, msg)
key = "#{level}_#{type}".to_sym
return if config[key].nil?
to_check = config[key].to_i
send(level, msg % [element, value, config[:bucket_name]]) if operator.call type, value, to_check
end
def run
aws_config = {}
if (config[:key_name].nil? && config[:key_prefix].nil?) || (!config[:key_name].nil? && !config[:key_prefix].nil?)
critical 'Need one option between "key_name" and "key_prefix"'
end
if config[:use_iam_role].nil?
aws_config[:access_key_id] = config[:aws_access_key]
aws_config[:secret_access_key] = config[:aws_secret_access_key]
end
s3 = Aws::S3::Client.new(aws_config.merge!(region: config[:aws_region]))
begin
if !config[:key_name].nil?
key_search = config[:key_name]
key_fullname = key_search
output = s3.head_object(bucket: config[:bucket_name], key: key_search)
age = Time.now.to_i - output[:last_modified].to_i
size = output[:content_length]
elsif !config[:key_prefix].nil?
key_search = config[:key_prefix]
output = s3.list_objects(bucket: config[:bucket_name], prefix: key_search)
output = output.next_page while output.next_page?
if output.contents.size.to_i < 1
critical "Object with prefix \"#{key_search}\" not found in bucket #{config[:bucket_name]}"
end
if output.contents.size.to_i > 1
if config[:no_crit_on_multiple_objects].nil?
critical "Your prefix \"#{key_search}\" return too much files, you need to be more specific"
else
output.contents.sort_by!(&:last_modified).reverse!
end
end
key_fullname = output.contents[0].key
age = Time.now.to_i - output.contents[0].last_modified.to_i
size = output.contents[0].size
end
%i[critical warning].each do |level|
run_check('age', level, age, key_fullname, 'S3 object %s is %s seconds old (bucket %s)')
end
if size.zero?
critical "S3 object #{key_fullname} is empty (bucket #{config[:bucket_name]})" unless config[:ok_zero_size]
else
%i[critical warning].each do |level|
run_check('size', level, size, key_fullname, 'S3 %s object\'size : %s octets (bucket %s)')
end
end
ok("S3 object #{key_fullname} exists in bucket #{config[:bucket_name]}")
rescue Aws::S3::Errors::NotFound => _
critical "S3 object #{key_fullname} not found in bucket #{config[:bucket_name]}"
rescue StandardError => e
critical "S3 object #{key_fullname} in bucket #{config[:bucket_name]} - #{e.message} - #{e.backtrace}"
end
end
end