Skip to content

Instantly share code, notes, and snippets.

@orangewolf
Created August 13, 2025 22:11
Show Gist options
  • Save orangewolf/a9fa5040cc2722071172651c55b91c33 to your computer and use it in GitHub Desktop.
Save orangewolf/a9fa5040cc2722071172651c55b91c33 to your computer and use it in GitHub Desktop.
Script to find all bitnami images in the current k8s cluster
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'json'
require 'open3'
require 'optparse'
# Ruby script to find Bitnami-based container images deployed in Kubernetes cluster
# Shows namespace, pod/deployment info, and associated Helm chart when available
class BitnamiFinder
# ANSI color codes
COLORS = {
red: "\033[0;31m",
green: "\033[0;32m",
blue: "\033[0;34m",
yellow: "\033[1;33m",
reset: "\033[0m"
}.freeze
def initialize(options = {})
@options = options
@verbose = options[:verbose] || false
@summary_only = options[:summary_only] || false
@namespace_filter = options[:namespace]
end
def run
print_header
check_prerequisites
show_cluster_info
unless @summary_only
find_bitnami_pods
find_bitnami_workloads
puts
end
print_summary
end
private
def print_header
puts colorize("==========================================", :blue)
puts colorize(" Bitnami Images in Kubernetes Cluster ", :blue)
puts colorize("==========================================", :blue)
puts
end
def colorize(text, color)
return text unless $stdout.tty?
"#{COLORS[color]}#{text}#{COLORS[:reset]}"
end
def check_prerequisites
unless command_exists?('kubectl')
puts colorize("Error: kubectl is not installed or not in PATH", :red)
exit 1
end
stdout, stderr, status = Open3.capture3('kubectl cluster-info')
unless status.success?
puts colorize("Error: Cannot connect to Kubernetes cluster", :red)
puts stderr if @verbose
exit 1
end
end
def command_exists?(command)
system("which #{command} > /dev/null 2>&1")
end
def show_cluster_info
current_context, = Open3.capture3('kubectl config current-context')
puts "#{colorize('Current cluster context:', :green)} #{current_context.strip}"
puts
end
def execute_kubectl(command)
stdout, stderr, status = Open3.capture3("kubectl #{command}")
unless status.success?
puts colorize("Error executing: kubectl #{command}", :red) if @verbose
puts stderr if @verbose
return nil
end
stdout
end
def get_helm_info(namespace, resource_type, resource_name)
# Try to get Helm labels
labels_cmd = "get #{resource_type} #{resource_name} -n #{namespace} -o jsonpath='{.metadata.labels}'"
labels_output = execute_kubectl(labels_cmd)
return "No Helm info" unless labels_output
# Try different Helm label formats
helm_chart = get_label_value(namespace, resource_type, resource_name, 'helm\.sh/chart') ||
get_label_value(namespace, resource_type, resource_name, 'chart')
helm_release = get_label_value(namespace, resource_type, resource_name, 'app\.kubernetes\.io/instance') ||
get_label_value(namespace, resource_type, resource_name, 'release')
if helm_release && helm_chart
"Helm: #{helm_release} (#{helm_chart})"
elsif helm_release
"Helm: #{helm_release}"
elsif helm_chart
"Chart: #{helm_chart}"
else
"No Helm info"
end
rescue
"No Helm info"
end
def get_label_value(namespace, resource_type, resource_name, label_key)
cmd = "get #{resource_type} #{resource_name} -n #{namespace} -o jsonpath='{.metadata.labels.#{label_key}}'"
output = execute_kubectl(cmd)
output&.strip&.empty? ? nil : output&.strip
end
def find_bitnami_pods
puts colorize("Searching for Bitnami images across all namespaces...", :green)
puts
namespace_selector = @namespace_filter ? "-n #{@namespace_filter}" : "--all-namespaces"
pods_json = execute_kubectl("get pods #{namespace_selector} -o json")
return unless pods_json
pods_data = JSON.parse(pods_json)
bitnami_pods = []
pods_data['items'].each do |pod|
bitnami_images = pod.dig('spec', 'containers')&.map { |c| c['image'] }&.select { |img| img.include?('bitnami') }
next if bitnami_images.nil? || bitnami_images.empty?
namespace = pod.dig('metadata', 'namespace')
name = pod.dig('metadata', 'name')
owner_refs = pod.dig('metadata', 'ownerReferences') || []
bitnami_pods << {
namespace: namespace,
name: name,
images: bitnami_images,
owner_refs: owner_refs
}
end
display_bitnami_resources(bitnami_pods, 'pod')
end
def find_bitnami_workloads
puts colorize("Checking Deployments, StatefulSets, and DaemonSets for Bitnami images...", :green)
puts
%w[deployment statefulset daemonset].each do |resource_type|
find_bitnami_in_resource_type(resource_type)
end
end
def find_bitnami_in_resource_type(resource_type)
namespace_selector = @namespace_filter ? "-n #{@namespace_filter}" : "--all-namespaces"
resources_json = execute_kubectl("get #{resource_type} #{namespace_selector} -o json")
return unless resources_json
begin
resources_data = JSON.parse(resources_json)
bitnami_resources = []
resources_data['items'].each do |resource|
containers = resource.dig('spec', 'template', 'spec', 'containers') || []
bitnami_images = containers.map { |c| c['image'] }.select { |img| img.include?('bitnami') }
next if bitnami_images.empty?
namespace = resource.dig('metadata', 'namespace')
name = resource.dig('metadata', 'name')
bitnami_resources << {
namespace: namespace,
name: name,
images: bitnami_images,
resource_type: resource_type
}
end
display_workload_resources(bitnami_resources)
rescue JSON::ParserError => e
puts colorize("Warning: Could not parse #{resource_type} JSON: #{e.message}", :yellow) if @verbose
end
end
def display_bitnami_resources(resources, type)
resources.each do |resource|
puts "#{colorize('Namespace:', :yellow)} #{resource[:namespace]}"
puts "#{colorize('Pod:', :yellow)} #{resource[:name]}"
# Handle owner references
if resource[:owner_refs].any?
owner = resource[:owner_refs].first
puts "#{colorize('Owner:', :yellow)} #{owner['kind']}/#{owner['name']}"
helm_info = get_helm_info(resource[:namespace], owner['kind'].downcase, owner['name'])
else
helm_info = get_helm_info(resource[:namespace], 'pod', resource[:name])
end
puts "#{colorize('Helm Info:', :yellow)} #{helm_info}"
puts colorize('Bitnami Images:', :yellow)
resource[:images].each do |image|
puts " - #{image}"
end
puts "---"
end
end
def display_workload_resources(resources)
resources.each do |resource|
puts "#{colorize('Namespace:', :yellow)} #{resource[:namespace]}"
puts "#{colorize('Resource:', :yellow)} #{resource[:resource_type]}/#{resource[:name]}"
helm_info = get_helm_info(resource[:namespace], resource[:resource_type], resource[:name])
puts "#{colorize('Helm Info:', :yellow)} #{helm_info}"
puts colorize('Bitnami Images:', :yellow)
resource[:images].each do |image|
puts " - #{image}"
end
puts "---"
end
end
def print_summary
puts colorize("========== SUMMARY ==========", :blue)
# Get all images from all pods
namespace_selector = @namespace_filter ? "-n #{@namespace_filter}" : "--all-namespaces"
all_images_output = execute_kubectl("get pods #{namespace_selector} -o jsonpath='{range .items[*]}{.spec.containers[*].image}{\" \"}{end}'")
return unless all_images_output
all_images = all_images_output.split(' ').select { |img| img.include?('bitnami') }
unique_images = all_images.uniq.sort
# Get namespace and pod counts
pods_json = execute_kubectl("get pods #{namespace_selector} -o json")
return unless pods_json
pods_data = JSON.parse(pods_json)
namespaces_with_bitnami = Set.new
pods_with_bitnami = 0
pods_data['items'].each do |pod|
bitnami_containers = pod.dig('spec', 'containers')&.select { |c| c['image'].include?('bitnami') }
if bitnami_containers && !bitnami_containers.empty?
namespaces_with_bitnami << pod.dig('metadata', 'namespace')
pods_with_bitnami += 1
end
end
puts "#{colorize('Unique Bitnami images:', :green)} #{unique_images.count}"
puts "#{colorize('Namespaces with Bitnami:', :green)} #{namespaces_with_bitnami.count}"
puts "#{colorize('Pods using Bitnami images:', :green)} #{pods_with_bitnami}"
puts
puts colorize('Most common Bitnami images:', :yellow)
image_counts = all_images.each_with_object(Hash.new(0)) { |img, hash| hash[img] += 1 }
image_counts.sort_by { |_, count| -count }.first(10).each do |image, count|
puts " #{count} x #{image}"
end
# Show namespace breakdown if not filtering by namespace
unless @namespace_filter
puts
puts colorize('Namespaces with Bitnami images:', :yellow)
namespaces_with_bitnami.sort.each do |ns|
puts " - #{ns}"
end
end
end
end
# Command line option parsing
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: #{$0} [options]"
opts.on("-v", "--verbose", "Run verbosely") do |v|
options[:verbose] = v
end
opts.on("-s", "--summary", "Show summary only") do |s|
options[:summary_only] = s
end
opts.on("-n", "--namespace NAMESPACE", "Filter by namespace") do |n|
options[:namespace] = n
end
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
end
end.parse!
# Run the finder
finder = BitnamiFinder.new(options)
finder.run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment