Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue where cleanup-stale-apps command fails to delete apps with volumesets #175

Merged
merged 2 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ Changes since the last non-beta release.

_Please add entries here for your pull requests that are not yet released._

### Fixed

- Fixed issue where `cleanup-stale-apps` command fails to delete apps with volumesets. [PR 175](https://github.com/shakacode/heroku-to-control-plane/pull/175) by [Rafael Gomes](https://github.com/rafaelgomesxyz).

rafaelgomesxyz marked this conversation as resolved.
Show resolved Hide resolved
## [2.0.0] - 2024-05-14

### BREAKING CHANGES
Expand Down
3 changes: 2 additions & 1 deletion docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ cpl cleanup-images -a $APP_NAME

### `cleanup-stale-apps`

- Deletes the whole app (GVC with all workloads and all images) for all stale apps
- Deletes the whole app (GVC with all workloads, all volumesets and all images) for all stale apps
- Also unbinds the app from the secrets policy, as long as both the identity and the policy exist (and are bound)
- Stale apps are identified based on the creation date of the latest image
- Specify the amount of days after an app should be considered stale through `stale_app_image_deployed_days` in the `.controlplane/controlplane.yml` file
- If `match_if_app_name_starts_with` is `true` in the `.controlplane/controlplane.yml` file, it will delete all stale apps that start with the name
Expand Down
26 changes: 8 additions & 18 deletions lib/command/cleanup_stale_apps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ class CleanupStaleApps < Base
app_option(required: true),
skip_confirm_option
].freeze
DESCRIPTION = "Deletes the whole app (GVC with all workloads and all images) for all stale apps"
DESCRIPTION = "Deletes the whole app (GVC with all workloads, all volumesets and all images) for all stale apps"
LONG_DESCRIPTION = <<~DESC
- Deletes the whole app (GVC with all workloads and all images) for all stale apps
- Deletes the whole app (GVC with all workloads, all volumesets and all images) for all stale apps
- Also unbinds the app from the secrets policy, as long as both the identity and the policy exist (and are bound)
- Stale apps are identified based on the creation date of the latest image
- Specify the amount of days after an app should be considered stale through `stale_app_image_deployed_days` in the `.controlplane/controlplane.yml` file
- If `match_if_app_name_starts_with` is `true` in the `.controlplane/controlplane.yml` file, it will delete all stale apps that start with the name
Expand All @@ -28,8 +29,8 @@ def call # rubocop:disable Metrics/MethodLength

progress.puts
stale_apps.each do |app|
delete_gvc(app)
delete_images(app)
delete_app(app[:name])
progress.puts
rafaelgomesxyz marked this conversation as resolved.
Show resolved Hide resolved
end
end

Expand Down Expand Up @@ -57,8 +58,7 @@ def stale_apps # rubocop:disable Metrics/MethodLength

apps.push({
name: app_name,
date: created_date,
images: images.map { |current_image| current_image["name"] }
date: created_date
})
end

Expand All @@ -72,18 +72,8 @@ def confirm_delete
Shell.confirm("\nAre you sure you want to delete these #{stale_apps.length} apps?")
end

def delete_gvc(app)
step("Deleting app '#{app[:name]}'") do
cp.gvc_delete(app[:name])
end
end

def delete_images(app)
app[:images].each do |image|
step("Deleting image '#{image}'") do
cp.image_delete(image)
end
end
def delete_app(app)
Cpl::Cli.start(["delete", "-a", app, "--yes"])
Copy link
Collaborator Author

@rafaelgomesxyz rafaelgomesxyz May 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simply call the delete command instead of implementing its own delete methods.

It's ok to pass --yes here because this is only called after confirm_delete:

return unless confirm_delete
progress.puts
stale_apps.each do |app|
delete_app(app[:name])

So the confirmation step will already have been handled by the time we get here.

rafaelgomesxyz marked this conversation as resolved.
Show resolved Hide resolved
end
end
end
20 changes: 11 additions & 9 deletions lib/command/delete.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ def call
private

def delete_single_workload(workload)
return progress.puts("Workload '#{workload}' does not exist.") if cp.fetch_workload(workload).nil?
if cp.fetch_workload(workload).nil?
return progress.puts("Workload '#{workload}' does not exist in app '#{config.app}'.")
end
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All changes in this file are simply to add, e.g., " app 'my-app'", at the end of the progress messages, so that it's clear which app each item being deleted belongs to.

return unless confirm_delete(workload)

delete_workload(workload)
Expand All @@ -57,19 +59,19 @@ def delete_whole_app

def check_volumesets
@volumesets = cp.fetch_volumesets["items"]
return progress.puts("No volumesets to delete.") unless @volumesets.any?
return progress.puts("No volumesets to delete from app '#{config.app}'.") unless @volumesets.any?

message = "The following volumesets will be deleted along with the app:"
message = "The following volumesets will be deleted along with the app '#{config.app}':"
volumesets_list = @volumesets.map { |volumeset| "- #{volumeset['name']}" }.join("\n")
progress.puts("#{Shell.color(message, :red)}\n#{volumesets_list}\n\n")
end

def check_images
@images = cp.query_images["items"]
.select { |image| image["name"].start_with?("#{config.app}:") }
return progress.puts("No images to delete.") unless @images.any?
return progress.puts("No images to delete from app '#{config.app}'.") unless @images.any?

message = "The following images will be deleted along with the app:"
message = "The following images will be deleted along with the app '#{config.app}':"
images_list = @images.map { |image| "- #{image['name']}" }.join("\n")
progress.puts("#{Shell.color(message, :red)}\n#{images_list}\n\n")
end
Expand All @@ -91,14 +93,14 @@ def delete_gvc
end

def delete_workload(workload)
step("Deleting workload '#{workload}'") do
step("Deleting workload '#{workload}' from app '#{config.app}'") do
cp.delete_workload(workload)
end
end

def delete_volumesets
@volumesets.each do |volumeset|
step("Deleting volumeset '#{volumeset['name']}'") do
step("Deleting volumeset '#{volumeset['name']}' from app '#{config.app}'") do
# If the volumeset is attached to a workload, we need to delete the workload first
workload = volumeset.dig("status", "usedByWorkload")&.split("/")&.last
cp.delete_workload(workload) if workload
Expand All @@ -110,7 +112,7 @@ def delete_volumesets

def delete_images
@images.each do |image|
step("Deleting image '#{image['name']}'") do
step("Deleting image '#{image['name']}' from app '#{config.app}'") do
cp.image_delete(image["name"])
end
end
Expand All @@ -127,7 +129,7 @@ def unbind_identity_from_policy
end
return unless is_bound

step("Unbinding identity from policy") do
step("Unbinding identity from policy for app '#{config.app}'") do
cp.unbind_identity_from_policy(app_identity_link, app_secrets_policy)
end
end
Expand Down
16 changes: 10 additions & 6 deletions spec/command/cleanup_stale_apps_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
let!(:app2) { dummy_test_app("with-stale-app-image-deployed-days") }

before do
run_cpl_command!("apply-template", "app", "-a", app1)
run_cpl_command!("apply-template", "app", "-a", app2)
run_cpl_command!("apply-template", "app", "postgres-with-volume", "-a", app1)
run_cpl_command!("apply-template", "app", "postgres-with-volume", "-a", app2)
run_cpl_command!("build-image", "-a", app1)
run_cpl_command!("build-image", "-a", app2)
end
Expand Down Expand Up @@ -64,10 +64,12 @@

expect(Shell).to have_received(:confirm).once
expect(result[:status]).to eq(0)
expect(result[:stderr]).to match(/Deleting volumeset 'postgres-volume' from app '#{app1}'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting app '#{app1}'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting image '#{app1}:1'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting image '#{app1}:1' from app '#{app1}'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting volumeset 'postgres-volume' from app '#{app2}'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting app '#{app2}'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting image '#{app2}:1'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting image '#{app2}:1' from app '#{app2}'[.]+? done!/)
end

it "skips confirmation and deletes stale apps", :slow do
Expand All @@ -79,10 +81,12 @@

expect(Shell).not_to have_received(:confirm)
expect(result[:status]).to eq(0)
expect(result[:stderr]).to match(/Deleting volumeset 'postgres-volume' from app '#{app1}'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting app '#{app1}'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting image '#{app1}:1'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting image '#{app1}:1' from app '#{app1}'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting volumeset 'postgres-volume' from app '#{app2}'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting app '#{app2}'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting image '#{app2}:1'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting image '#{app2}:1' from app '#{app2}'[.]+? done!/)
end
end

Expand Down
26 changes: 13 additions & 13 deletions spec/command/delete_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@

expect(Shell).to have_received(:confirm).once
expect(result[:status]).to eq(0)
expect(result[:stderr]).to include("No volumesets to delete")
expect(result[:stderr]).to include("No images to delete")
expect(result[:stderr]).to include("No volumesets to delete from app '#{app}'")
expect(result[:stderr]).to include("No images to delete from app '#{app}'")
expect(result[:stderr]).not_to include("Deleting app")
end

Expand All @@ -44,8 +44,8 @@

expect(Shell).to have_received(:confirm).once
expect(result[:status]).to eq(0)
expect(result[:stderr]).to include("No volumesets to delete")
expect(result[:stderr]).to include("No images to delete")
expect(result[:stderr]).to include("No volumesets to delete from app '#{app}'")
expect(result[:stderr]).to include("No images to delete from app '#{app}'")
expect(result[:stderr]).to match(/Deleting app '#{app}'[.]+? done!/)
end

Expand All @@ -56,8 +56,8 @@

expect(Shell).not_to have_received(:confirm)
expect(result[:status]).to eq(0)
expect(result[:stderr]).to include("No volumesets to delete")
expect(result[:stderr]).to include("No images to delete")
expect(result[:stderr]).to include("No volumesets to delete from app '#{app}'")
expect(result[:stderr]).to include("No images to delete from app '#{app}'")
expect(result[:stderr]).to match(/Deleting app '#{app}'[.]+? done!/)
end
end
Expand All @@ -81,10 +81,10 @@

expect(Shell).to have_received(:confirm).once
expect(result[:status]).to eq(0)
expect(result[:stderr]).to match(/Deleting volumeset 'detached-volume'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting volumeset 'postgres-volume'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting volumeset 'detached-volume' from app '#{app}'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting volumeset 'postgres-volume' from app '#{app}'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting app '#{app}'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting image '#{app}:1'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting image '#{app}:1' from app '#{app}'[.]+? done!/)
end
end

Expand All @@ -103,7 +103,7 @@
result = run_cpl_command("delete", "-a", app, "--workload", "rails")

expect(result[:status]).to eq(0)
expect(result[:stderr]).to include("Workload 'rails' does not exist")
expect(result[:stderr]).to include("Workload 'rails' does not exist in app '#{app}'")
end
end

Expand Down Expand Up @@ -135,7 +135,7 @@

expect(Shell).to have_received(:confirm).once
expect(result[:status]).to eq(0)
expect(result[:stderr]).to match(/Deleting workload 'rails'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting workload 'rails' from app '#{app}'[.]+? done!/)
end

it "skips confirmation and deletes workload" do
Expand All @@ -145,7 +145,7 @@

expect(Shell).not_to have_received(:confirm)
expect(result[:status]).to eq(0)
expect(result[:stderr]).to match(/Deleting workload 'rails'[.]+? done!/)
expect(result[:stderr]).to match(/Deleting workload 'rails' from app '#{app}'[.]+? done!/)
end
end

Expand Down Expand Up @@ -223,7 +223,7 @@
expect(Shell).to have_received(:confirm).once
expect(result[:status]).to eq(0)
expect(result[:stderr]).to match(/Deleting app '#{app}'[.]+? done!/)
expect(result[:stderr]).to match(/Unbinding identity from policy[.]+? done!/)
expect(result[:stderr]).to match(/Unbinding identity from policy for app '#{app}'[.]+? done!/)
end
end
end
Loading