volume_sweeper 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3b347010ebacef693dd8fe9917e0ef3165dadeed8f79076e61ab5fa051ae6f6a
4
+ data.tar.gz: 613ff41df273c38ae7b8a3c55176c284716aa64be804b1fd86b4d824cbdfff90
5
+ SHA512:
6
+ metadata.gz: 3ac1b14b8a25b824a0832b423a20ba0ff8d00653f70eebb83d6e266fced137096b80f8ce82c5496eeffa3f967d194b77a32f72641411c97bd10b14a8f35cb523
7
+ data.tar.gz: 8f3212d22053ba1c3ed803dd659929f635e7b4ac7c73f618263ce27a3bc83439fd72f47f358344c5e276d59be817f29f6f5b73cf1bb2d2bb5aee96816e1793ba
data/.dockerignore ADDED
@@ -0,0 +1,220 @@
1
+ .git
2
+ .gitignore
3
+
4
+ ### Git ###
5
+
6
+ # $ git config --global mergetool.keepBackup false
7
+ *.orig
8
+
9
+
10
+ *.BACKUP.*
11
+ *.BASE.*
12
+ *.LOCAL.*
13
+ *.REMOTE.*
14
+ *_BACKUP_*.txt
15
+ *_BASE_*.txt
16
+ *_LOCAL_*.txt
17
+ *_REMOTE_*.txt
18
+
19
+ ### JetBrains+all ###
20
+ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
21
+ # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
22
+
23
+ # User-specific stuff
24
+ .idea/**/workspace.xml
25
+ .idea/**/tasks.xml
26
+ .idea/**/usage.statistics.xml
27
+ .idea/**/dictionaries
28
+ .idea/**/shelf
29
+
30
+ # Generated files
31
+ .idea/**/contentModel.xml
32
+
33
+ # Sensitive or high-churn files
34
+ .idea/**/dataSources/
35
+ .idea/**/dataSources.ids
36
+ .idea/**/dataSources.local.xml
37
+ .idea/**/sqlDataSources.xml
38
+ .idea/**/dynamic.xml
39
+ .idea/**/uiDesigner.xml
40
+ .idea/**/dbnavigator.xml
41
+
42
+ # Gradle
43
+ .idea/**/gradle.xml
44
+ .idea/**/libraries
45
+
46
+ # Gradle and Maven with auto-import
47
+ # When using Gradle or Maven with auto-import, you should exclude module files,
48
+ # since they will be recreated, and may cause churn. Uncomment if using
49
+ # auto-import.
50
+ # .idea/modules.xml
51
+ # .idea/*.iml
52
+ # .idea/modules
53
+ # *.iml
54
+ # *.ipr
55
+
56
+ # CMake
57
+ cmake-build-*/
58
+
59
+ # Mongo Explorer plugin
60
+ .idea/**/mongoSettings.xml
61
+
62
+ # File-based project format
63
+ *.iws
64
+
65
+ # IntelliJ
66
+ out/
67
+
68
+ # mpeltonen/sbt-idea plugin
69
+ .idea_modules/
70
+
71
+ # JIRA plugin
72
+ atlassian-ide-plugin.xml
73
+
74
+ # Cursive Clojure plugin
75
+ .idea/replstate.xml
76
+
77
+ # Crashlytics plugin (for Android Studio and IntelliJ)
78
+ com_crashlytics_export_strings.xml
79
+ crashlytics.properties
80
+ crashlytics-build.properties
81
+ fabric.properties
82
+
83
+ # Editor-based Rest Client
84
+ .idea/httpRequests
85
+
86
+ # Android studio 3.1+ serialized cache file
87
+ .idea/caches/build_file_checksums.ser
88
+
89
+ ### JetBrains+all Patch ###
90
+ # Ignores the whole .idea folder and all .iml files
91
+ # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
92
+
93
+ .idea/
94
+
95
+ # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
96
+
97
+ *.iml
98
+ modules.xml
99
+ .idea/misc.xml
100
+ *.ipr
101
+
102
+ # Sonarlint plugin
103
+ .idea/sonarlint
104
+
105
+ ### Rails ###
106
+ *.rbc
107
+ capybara-*.html
108
+ .rspec
109
+ /db/*.sqlite3
110
+ /db/*.sqlite3-journal
111
+ /public/system
112
+ /coverage/
113
+ /spec/tmp
114
+ rerun.txt
115
+ pickle-email-*.html
116
+
117
+ # Ignore all logfiles and tempfiles.
118
+ /log/*
119
+ /tmp/*
120
+ !/log/.keep
121
+ !/tmp/.keep
122
+
123
+ # TODO Comment out this rule if you are OK with secrets being uploaded to the repo
124
+ config/initializers/secret_token.rb
125
+ config/master.key
126
+
127
+ # Only include if you have production secrets in this file, which is no longer a Rails default
128
+ # config/secrets.yml
129
+
130
+ # dotenv
131
+ # TODO Comment out this rule if environment variables can be committed
132
+ .env
133
+
134
+ ## Environment normalization:
135
+ /.bundle
136
+ /vendor/bundle
137
+
138
+ # these should all be checked in to normalize the environment:
139
+ # Gemfile.lock, .ruby-version, .ruby-gemset
140
+
141
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
142
+ .rvmrc
143
+
144
+ # if using bower-rails ignore default bower_components path bower.json files
145
+ /vendor/assets/bower_components
146
+ *.bowerrc
147
+ bower.json
148
+
149
+ # Ignore pow environment settings
150
+ .powenv
151
+
152
+ # Ignore Byebug command history file.
153
+ .byebug_history
154
+
155
+ # Ignore node_modules
156
+ node_modules/
157
+
158
+ # Ignore precompiled javascript packs
159
+ /public/packs
160
+ /public/packs-test
161
+ /public/assets
162
+
163
+ # Ignore yarn files
164
+ /yarn-error.log
165
+ yarn-debug.log*
166
+ .yarn-integrity
167
+
168
+ # Ignore uploaded files in development
169
+ /storage/*
170
+ !/storage/.keep
171
+
172
+ ### Ruby ###
173
+ *.gem
174
+ /.config
175
+ /InstalledFiles
176
+ /pkg/
177
+ /spec/reports/
178
+ /spec/examples.txt
179
+ /test/tmp/
180
+ /test/version_tmp/
181
+ /tmp/
182
+
183
+ # Used by dotenv library to load environment variables.
184
+ # .env
185
+
186
+ # Ignore Byebug command history file.
187
+
188
+ ## Specific to RubyMotion:
189
+ .dat*
190
+ .repl_history
191
+ build/
192
+ *.bridgesupport
193
+ build-iPhoneOS/
194
+ build-iPhoneSimulator/
195
+
196
+ ## Specific to RubyMotion (use of CocoaPods):
197
+ #
198
+ # We recommend against adding the Pods directory to your .gitignore. However
199
+ # you should judge for yourself, the pros and cons are mentioned at:
200
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
201
+ # vendor/Pods/
202
+
203
+ ## Documentation cache and generated files:
204
+ /.yardoc/
205
+ /_yardoc/
206
+ /doc/
207
+ /rdoc/
208
+
209
+ /.bundle/
210
+ /lib/bundler/man/
211
+
212
+ # for a library or gem, you might want to ignore these files since the code is
213
+ # intended to run in multiple environments; otherwise, check them in:
214
+ # Gemfile.lock
215
+ # .ruby-version
216
+ # .ruby-gemset
217
+
218
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
219
+
220
+ # End of https://www.gitignore.io/api/git,ruby,rails,jetbrains+all
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ volume_sweeper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.1.4
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/testdouble/standard
3
+ ruby_version: 3.1.4
data/Dockerfile ADDED
@@ -0,0 +1,20 @@
1
+ # syntax=docker/dockerfile:1
2
+
3
+ FROM ruby:3.1.4 AS base
4
+
5
+ FROM base AS dependencies
6
+ RUN apt-get update -qq && apt-get install -y gcc cron
7
+
8
+ FROM dependencies as build
9
+ USER root
10
+ WORKDIR /app
11
+ COPY . .
12
+ RUN gem install bundler
13
+ RUN bundle install --without development
14
+
15
+ FROM build as runtime
16
+
17
+ COPY --from=build /app /app
18
+
19
+ EXPOSE 3000
20
+ CMD ["sh", "-c", "./bin/volume_sweeper --help"]
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Abdullah Barrak
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # Volume Sweeper
2
+ [![CI (tests)](https://github.com/abarrak/volume_sweeper/actions/workflows/ci.yml/badge.svg)](https://github.com/abarrak/volume_sweeper/actions/workflows/ci.yml) [![Gem Version](https://badge.fury.io/rb/volume_sweeper.svg)](https://badge.fury.io/rb/volume_sweeper) [![Test Coverage](https://api.codeclimate.com/v1/badges/b9d24a336e67236937dd/test_coverage)](https://codeclimate.com/github/abarrak/volume_sweeper/test_coverage) [![Maintainability](https://api.codeclimate.com/v1/badges/b9d24a336e67236937dd/maintainability)](https://codeclimate.com/github/abarrak/volume_sweeper/maintainability) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
3
+
4
+
5
+ A tool to scan and clean cloud infrastruture for unattached block volumes without kubernetes clusters persistent volumes.
6
+
7
+ ## Supported Clouds
8
+
9
+ - [x] OCI
10
+ - [ ] AWS.
11
+ - [ ] GCP.
12
+
13
+ ## Supported Kubernetes
14
+
15
+ Any distributions + v1.19.
16
+
17
+ ## Prerequisits
18
+
19
+ 1. Kubernetes: a service account with read/update access to the cluster is required, scoped to `PV` resources.
20
+ 2. Cloud: access is required for block volumes service (BV) with read and delete roles.
21
+
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ $ gem install volume_sweeper
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ To scan and generate a report:
32
+
33
+ ```bash
34
+ volume_sweeper --account-id <ID> --cloud aws|oci
35
+ ```
36
+
37
+ To apply deletion for unattached block volumes:
38
+
39
+ ```bash
40
+ volume_sweeper --mode delete
41
+ ```
42
+
43
+ ## Contributing
44
+
45
+ Bug reports and pull requests are welcome on GitHub at https://github.com/abarrak/volume_sweeper.
46
+
47
+ ## License
48
+
49
+ [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ require "standard/rake"
7
+
8
+ task default: %i[spec standard]
@@ -0,0 +1,57 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+ require 'cowsay'
4
+ require_relative 'utils/log'
5
+
6
+ module VolumeSweeper
7
+ class Cli
8
+ class << self
9
+ attr_reader :options
10
+ ##
11
+ # A simple cli scriptlet to proccess command line arguments and pass them to
12
+ # the core component to run.
13
+ #
14
+ def run
15
+ print_banner
16
+ set_default_options
17
+ process_user_input
18
+ options
19
+ end
20
+
21
+ private
22
+
23
+ def print_banner
24
+ puts Cowsay::say('Volume Sweeper 1.0', 'cow'), ""
25
+ end
26
+
27
+ def process_user_input
28
+ OptionParser.new("== Usage: volume_sweeper.rb [options]") do |opt|
29
+ opt.on('-m', '--mode [MODE]', 'The run modes: either audit, or delete.') { |o| options.mode = o }
30
+ opt.on('-c', '--cloud [CLOUD]', 'Supported clouds: aws, oci.') { |o| options.cloud = o }
31
+ opt.on('-f', '--config-path [PATH]', 'The file location for cloud config file') { |o| options.config_path = o }
32
+ opt.on('-r', '--region [REGION]', 'The provider region of the account.') { |o| options.region = o }
33
+ opt.on('-a', '--account-id [Id]', 'The account or compartment Id.') { |o| options.account_id = o }
34
+ opt.on('-d', '--released-since [DAYS]', 'Volumes threshold duration') { |o| options.released_in_days = o }
35
+ opt.on('--kube-api-url [URL]', 'Kubernetes API URL') { |o| options.kube_api_url = o }
36
+ opt.on('--kube-api-ca-path [PATH]', 'Kubernetes API CA Cert Path') { |o| options.kube_api_ca_path = o }
37
+ opt.on('--kube-api-token [TOKEN]', 'Kubernetes API TOKEN (base64 formatted)') { |o| options.kube_api_token = o }
38
+ opt.on('--notification_subject [TEXT]', 'The message subject.') { |o| options.notification_subject = o }
39
+ opt.on('--smtp-host [HOST]', '') { |o| options.smtp_host = o }
40
+ opt.on('--smtp-port [PORT]', '') { |o| options.smtp_port = o }
41
+ opt.on('--smtp-username [USER]', '') { |o| options.smtp_username = o }
42
+ opt.on('--smtp-password [PASS]', '') { |o| options.smtp_password = o }
43
+ opt.on('--smtp-tls [ENABLE_TLS_FLAG]', '') { |o| options.smtp_tls = o }
44
+ opt.on('--smtp-sender [SENDER]', '') { |o| options.smtp_sender = o }
45
+ opt.on('--smtp-receiver [RECEIVER]', '') { |o| options.smtp_receiver = o }
46
+ opt.on('--ms-teams-webhook [URL]', '') { |o| options.ms_teams_webhook = o }
47
+ opt.on('-h', '--help') { |_| puts "", opt, "-" * 34; exit 0 }
48
+ end.parse!
49
+ end
50
+
51
+ def set_default_options
52
+ @options = OpenStruct.new
53
+ end
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,56 @@
1
+ require "active_support/core_ext/object/blank"
2
+ require_relative "utils/log"
3
+
4
+ module VolumeSweeper
5
+ module Comparer
6
+ LIST_SEP = "\n "
7
+ ##
8
+ # Compare unattached block volumes against kubernetes persistent volumes
9
+ # in order to avoid any block volume that as volumeHandler reference,
10
+ # even if not bound to instance.
11
+ #
12
+ # === Docs:
13
+ #
14
+ # The algorithm goes as follows:
15
+ # ```
16
+ # FOR each_cluster IN oci:
17
+ # A] Fetch PVs (name, ocid)
18
+ # B] Fetch BLOCK VOL where attachment = nil
19
+ # C] Compare A ^ B to Extract Bx NOT IN Ax
20
+ # THEN:
21
+ # DEL [C] result
22
+ # End
23
+ # ```
24
+ #
25
+ def self.process block_volumes, persistent_volumes
26
+ unused_volumes = []
27
+ active_volumes = []
28
+ counters = { active: 0, unused: 0 }
29
+
30
+ return {} if block_volumes.blank? || persistent_volumes.blank?
31
+
32
+ block_volumes.each do |vol|
33
+ if persistent_volumes.any? { |p| p[:volumeHandle]&.strip == vol&.strip }
34
+ counters[:active] += 1
35
+ active_volumes << vol
36
+ else
37
+ counters[:unused] += 1
38
+ unused_volumes << vol
39
+ end
40
+ end
41
+
42
+ seperator = -> (str) { str.join(LIST_SEP).prepend LIST_SEP }
43
+
44
+ active_list = active_volumes.any? ? seperator.call(active_volumes) : 'None'
45
+ unused_list = unused_volumes.any? ? seperator.call(unused_volumes) : 'None'
46
+
47
+ Utils::Log.instance.msg "=> Found #{counters[:active]} still in use."
48
+ Utils::Log.instance.msg "=> Found #{counters[:unused]} unused and should be terminated."
49
+ Utils::Log.instance.msg "=> Details:"
50
+ Utils::Log.instance.msg "===> Active: ", active_list
51
+ Utils::Log.instance.msg "===> Unused: ", unused_list
52
+
53
+ { active_ids: active_volumes, unused_ids: unused_volumes }
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,73 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+ require_relative 'cli'
3
+ require_relative 'comparer'
4
+ require_relative 'providers/aws'
5
+ require_relative 'providers/oci'
6
+ require_relative 'kube/client'
7
+ require_relative 'utils/log'
8
+ require_relative 'utils/notification'
9
+ require_relative 'utils/notification_formatter'
10
+
11
+ module VolumeSweeper
12
+ module Core
13
+ def self.process
14
+ # Process user input ..
15
+ opts = VolumeSweeper::Cli.run
16
+ cloud = opts.cloud
17
+ mode = opts.mode&.to_sym
18
+ options = {
19
+ config_path: opts.config_path,
20
+ account_id: opts.account_id,
21
+ region: opts.region,
22
+ mode: opts.mode,
23
+ kube_api_url: opts.kube_api_url,
24
+ kube_api_ca_path: opts.kube_api_ca_path,
25
+ kube_api_token: opts.kube_api_token,
26
+ notification_subject: opts.notification_subject,
27
+ smtp_host: opts.smtp_host,
28
+ smtp_port: opts.smtp_port,
29
+ smtp_username: opts.smtp_username,
30
+ smtp_password: opts.smtp_password,
31
+ smtp_tls: opts.smtp_tls,
32
+ smtp_sender: opts.smtp_sender,
33
+ smtp_receiver: opts.smtp_receiver,
34
+ ms_teams_webhook: opts.ms_teams_webhook
35
+ }
36
+
37
+ unless cloud.nil? || %w{oci aws}.include?(cloud)
38
+ Utils::Log.instance.msg "No could provider is chosen", level: :fatal
39
+ exit
40
+ end
41
+
42
+ # Build and run provider checks ..
43
+ klass = cloud.capitalize
44
+ provider = "VolumeSweeper::Providers::#{klass}".constantize.new **options
45
+ active_count, block_vols = provider.scan_block_volumes
46
+ block_vols.map! { |v| v[:id] }
47
+
48
+ # Build and run kubernetes checks ..
49
+ kube_client = VolumeSweeper::Kube::Client.new **options
50
+ cluster_vols = kube_client.fetch_pesistent_volumes
51
+
52
+ # Prepare notification layer ..
53
+ notifier = VolumeSweeper::Utils::Notification.new **options
54
+ formatter = VolumeSweeper::Utils::NotificationFormatter.new provider.base_link, mode
55
+
56
+ # Run cross reference checks.
57
+ results = Comparer.process block_vols, cluster_vols
58
+
59
+ # Send notice messages.
60
+ message = formatter.formlate_meessage results, active_count: active_count
61
+ notifier.send_ms_teams_notice message
62
+ notifier.send_mail message if results[:unused_ids]&.any?
63
+
64
+ # Then, clean up any unattached block volume without PV bound to.
65
+ provider.delete_block_volumes results[:unused_ids]
66
+
67
+ # Wait for plaform logs aggregation.
68
+ sleep 30
69
+
70
+ VolumeSweeper::Utils::Log.instance.msg "Done !"
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,138 @@
1
+ require "active_support/core_ext/object/blank"
2
+ require "base64"
3
+ require "kubeclient"
4
+ require_relative "../utils/log"
5
+
6
+ module VolumeSweeper
7
+ module Kube
8
+ ##
9
+ #
10
+ # This class enables interation with the kube apis using 2 different modes.
11
+ #
12
+ # 1. In-cluster acccess.
13
+ # Assuming the code will run in the cluster it targets,
14
+ # So the main defaults that pods that mount from service account
15
+ # secret like `token` and `ca.cert` are used in this case.
16
+ #
17
+ # 2. External cluster access.
18
+ # Pass the environment variables below to access outside cluster
19
+ # * KUBE_API_URL
20
+ # * KUBE_API_TOKEN
21
+ # * KUBE_API_CA_PATH
22
+ # Or their kwargs counterpart to constructor.
23
+ # * :kube_api_url
24
+ # * :kube_api_token [base64 formatted]
25
+ # * :kube_api_ca_path
26
+ #
27
+ # The env vars take the highest precedence.
28
+ #
29
+ class Client
30
+ def initialize **kwargs
31
+ @run_mode = kwargs[:mode]&.to_sym || :audit
32
+ @api_url = kwargs[:kube_api_url]
33
+ @api_token = kwargs[:kube_api_token]
34
+ @api_ca_path = kwargs[:kube_api_ca_path]
35
+
36
+ @log = Utils::Log.instance
37
+ prepare_kube_client
38
+ end
39
+
40
+ def fetch_pesistent_volumes
41
+ resources = []
42
+ make_api_call :get_persistent_volumes do |i|
43
+ resources << format_response_attrs(i)
44
+ end
45
+ @log.msg "kube: Collected #{resources.size} persisted volumes from the cluster."
46
+
47
+ resources
48
+ end
49
+
50
+ def delete_released_persistent_volumes age_in_days: 10
51
+ names = []
52
+ make_api_call :get_persistent_volumes do |i|
53
+ names << i[:metadat][:name] if i.dig(:status, :phase) == "Released"
54
+ end
55
+ @log.msg "kube: Collected #{resources.size} released persisted volumes."
56
+
57
+ return if names.blank? || @run_mode != :delete
58
+
59
+ # Do actual deletion for released persistent volumes.
60
+ # TODO: use last transistion timestamp if possible.
61
+ @log.msg "kube: Looping over #{names.size} persisted volumes to be deleted sequentially."
62
+ names.each do |pv|
63
+ @log.msg "kube: deleting #{names} persisted volume .."
64
+ @client.delete_pesistent_volume pv
65
+ sleep 2
66
+ end
67
+ @log.msg "kube: completed deletion of #{names.size} persisted volume."
68
+ end
69
+
70
+ protected
71
+
72
+ def prepare_kube_client
73
+ url = "#{build_cluster_url}/api"
74
+ @client ||= Kubeclient::Client.new url, "v1",
75
+ auth_options: build_auth_config,
76
+ ssl_options: build_ssl_config
77
+ @log.msg "Kube: running in :#{@run_mode} mode"
78
+ end
79
+
80
+ def build_cluster_url
81
+ default_cluster_url = "https://kubernetes.default.svc"
82
+ ENV.fetch("KUBE_API_URL", @api_url || default_cluster_url)
83
+ end
84
+
85
+ def build_ssl_config
86
+ default_ca_path = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
87
+ cluster_ca_path = ENV.fetch("KUBE_API_CA_PATH", @api_ca_path || default_ca_path)
88
+
89
+ Hash.new.tap do |h|
90
+ if File.exist? cluster_ca_path
91
+ h[:ca_file] = cluster_ca_path
92
+ else
93
+ h[:verify_ssl] = OpenSSL::SSL::VERIFY_NONE
94
+ end
95
+ end
96
+ end
97
+
98
+ def build_auth_config
99
+ cluster_token = ENV["KUBE_API_SECRET"]
100
+ default_token_path = "/var/run/secrets/kubernetes.io/serviceaccount/token"
101
+ Hash.new.tap do |h|
102
+ if cluster_token.present?
103
+ h[:bearer_token] = Base64.decode64 cluster_token
104
+ elsif @api_token.present?
105
+ h[:bearer_token] = Base64.decode64 @api_token
106
+ elsif File.exist? default_token_path
107
+ h[:bearer_token] = File.read default_token_path
108
+ end
109
+ end
110
+ end
111
+
112
+ def make_api_call method, **opts
113
+ continue = nil
114
+ { limit: 30 }.merge! opts
115
+ loop do
116
+ opts[:continue] = continue
117
+ output = @client.send method, **opts
118
+ continue = output.continue
119
+ output.map(&:to_h).collect do |i|
120
+ yield i
121
+ end
122
+ break if output.last?
123
+ end
124
+ end
125
+
126
+ def format_response_attrs item
127
+ {
128
+ name: item.dig(:metadat, :name),
129
+ status: item.dig(:status, :phase),
130
+ volumeHandle: item.dig(:spec, :csi, :volumeHandle),
131
+ pvc: item.dig(:spec, :claimRef, :name),
132
+ namespace: item.dig(:spec, :claimRef, :namespace)
133
+ }
134
+ end
135
+ end
136
+
137
+ end
138
+ end