sensu-plugins/sensu-plugins-aws

View on GitHub
bin/check-s3-object.rb

Summary

Maintainability
B
6 hrs
Test Coverage
#! /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