From d3bb5aff297ef62a73d673bc691881d555aab189 Mon Sep 17 00:00:00 2001 From: Tim Meusel Date: Fri, 22 Sep 2023 14:38:06 +0200 Subject: [PATCH] Delete stale servers after N hours --- README.md | 11 ++++++++++- beaker-hcloud.gemspec | 1 + lib/beaker/hypervisor/hcloud.rb | 28 +++++++++++++++++++++++++++ spec/beaker/hypervisor/hcloud_spec.rb | 6 ++++-- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1e99b49..3f1d13f 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,18 @@ are being created: # Cleanup In cases where the beaker process is killed before finishing, it may leave resources in Hetzner cloud. These will need to be manually deleted. - Look for servers in your project named exactly as the ones in your beaker host configuration and SSH keys with names beginning with `Beaker-`. +The gem will try to automatically delete old virtual machines. Every created +cloud instance gets a label `delete_vm_after: 'Fri, 22 Sep 2023 15:39:17 +0200'`. +By default this is the DateTime during VM creation + an hour. During each beaker +run, beaker-hcloud will check for existing VMs with a `delete_vm_after` label in +the past and delete them. + +To work properly, beaker needs to run on a regular basis. You can modify the +default of an hour by setting the `BEAKER_HCLOUD_DELETE_VM_AFTER` environment +variable to any positive integer. It will be interpreted as hours. + # Contributing Please refer to voxpupuli/beaker's [contributing](https://github.com/voxpupuli/beaker/blob/master/CONTRIBUTING.md) guide. diff --git a/beaker-hcloud.gemspec b/beaker-hcloud.gemspec index 45c3a3a..0cd20fc 100644 --- a/beaker-hcloud.gemspec +++ b/beaker-hcloud.gemspec @@ -21,6 +21,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'rspec', '~> 3.12' s.add_development_dependency 'voxpupuli-rubocop', '~> 2.0.0' + s.add_runtime_dependency 'activesupport', '~> 7.0', '>= 7.0.8' s.add_runtime_dependency 'bcrypt_pbkdf', '~> 1.0' s.add_runtime_dependency 'beaker', '~> 5.4' s.add_runtime_dependency 'ed25519', '~> 1.2' diff --git a/lib/beaker/hypervisor/hcloud.rb b/lib/beaker/hypervisor/hcloud.rb index a05e35b..8dcae2a 100644 --- a/lib/beaker/hypervisor/hcloud.rb +++ b/lib/beaker/hypervisor/hcloud.rb @@ -4,6 +4,11 @@ require 'ed25519' require 'bcrypt_pbkdf' +# required to calculate the VM deletion point +require 'active_support' +require 'active_support/time' +require 'active_support/core_ext/date' + require_relative '../../beaker-hcloud/ssh_data_patches' module Beaker @@ -15,10 +20,13 @@ def initialize(hosts, options) # rubocop:disable Lint/MissingSuper @options = options @logger = options[:logger] || Beaker::Logger.new @hosts = hosts + @delete_vm_after = ENV.fetch('BEAKER_HCLOUD_DELETE_VM_AFTER', 1).to_i + raise 'BEAKER_HCLOUD_DELETE_VM_AFTER needs to be a positive integer' unless @delete_vm_after.positive? raise 'You need to pass a token as BEAKER_HCLOUD_TOKEN environment variable' unless ENV['BEAKER_HCLOUD_TOKEN'] @client = ::Hcloud::Client.new(token: ENV.fetch('BEAKER_HCLOUD_TOKEN')) + delete_servers_if_required end def provision @@ -69,6 +77,25 @@ def create_ssh_key hcloud_ssh_key end + # we need to save the date as unix timestamp. Hetzner Cloud labels only support: + # "alphanumeric character ([a-z0-9A-Z]) with dashes (-), underscores (_), dots (.), and alphanumerics between." + # https://docs.hetzner.cloud/#labels + def vm_deletion_date + (DateTime.current + @delete_vm_after.hours).to_i.to_s + end + + # check if a server is too old (delete_vm_after is in the past) and delete it + # delete it also if it has no delete_vm_after label. All servers are supposed to have that + def delete_server_if_required(server) + server.destroy if server.labels['delete_vm_after'] && Time.now > Time.at(server.labels['delete_vm_after'].to_i) + server.destroy unless server.labels['delete_vm_after'] + end + + # iterate through all servers and delete them if required + def delete_servers_if_required + @client.servers.each { |server| delete_server_if_required(server) } + end + def create_server(host) @logger.notify "provisioning #{host.name}" location = host[:location] || 'nbg1' @@ -79,6 +106,7 @@ def create_server(host) server_type: server_type, image: host[:image], ssh_keys: [ssh_key_name], + labels: { delete_vm_after: vm_deletion_date }, ) while action.status == 'running' sleep 5 diff --git a/spec/beaker/hypervisor/hcloud_spec.rb b/spec/beaker/hypervisor/hcloud_spec.rb index e2eb309..ac3b86a 100644 --- a/spec/beaker/hypervisor/hcloud_spec.rb +++ b/spec/beaker/hypervisor/hcloud_spec.rb @@ -45,7 +45,8 @@ 'dns_ptr' => 'server1.example.com', }, }, - destroy: true) + destroy: true, + labels: { vm_delete_after: '1695385549' }) end let(:server2) do double(:server2, @@ -56,7 +57,8 @@ 'dns_ptr' => 'server2.example.com', }, }, - destroy: true) + destroy: true, + labels: { vm_delete_after: '1695385549' }) end let(:action_double) do double(:action, status: 'success')