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

lxd: Add support for forced deletion of projects #14343

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions client/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ type InstanceServer interface {
UpdateProject(name string, project api.ProjectPut, ETag string) (err error)
RenameProject(name string, project api.ProjectPost) (op Operation, err error)
DeleteProject(name string) (err error)
DeleteProjectForce(name string) (err error)

// Storage pool functions ("storage" API extension)
GetStoragePoolNames() (names []string, err error)
Expand Down
15 changes: 15 additions & 0 deletions client/lxd_projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,18 @@ func (r *ProtocolLXD) DeleteProject(name string) error {

return nil
}

// DeleteProjectForce deletes a project and everything inside of it.
func (r *ProtocolLXD) DeleteProjectForce(name string) error {
if !r.HasExtension("projects_force_delete") {
return fmt.Errorf("The server is missing the required \"projects_force_delete\" API extension")
}

// Send the request.
_, _, err := r.query("DELETE", fmt.Sprintf("/projects/%s?force=1", url.PathEscape(name)), nil, "")
if err != nil {
return err
}

return nil
}
5 changes: 5 additions & 0 deletions doc/api-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2535,3 +2535,8 @@ Adds a new {config:option}`device-unix-hotplug-device-conf:ownership.inherit` co
## `unix_device_hotplug_subsystem_device_option`

Adds a new {config:option}`device-unix-hotplug-device-conf:subsystem` configuration option for `unix-hotplug` devices. This adds support for detecting `unix-hotplug` devices by subsystem, and can be used in conjunction with {config:option}`device-unix-hotplug-device-conf:productid` and {config:option}`device-unix-hotplug-device-conf:vendorid`.

## `projects_force_delete`

This extends `DELETE /1.0/projects` to allow `?force=true` which will
delete everything inside of the project along with the project itself.
5 changes: 5 additions & 0 deletions doc/rest-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14567,6 +14567,11 @@ paths:
delete:
description: Removes the project.
operationId: project_delete
parameters:
- description: Delete project and related artifacts
in: query
name: force
type: boolean
produces:
- application/json
responses:
Expand Down
34 changes: 30 additions & 4 deletions lxc/project.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"bufio"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -181,6 +182,8 @@ func (c *cmdProjectCreate) run(cmd *cobra.Command, args []string) error {
type cmdProjectDelete struct {
global *cmdGlobal
project *cmdProject

flagForce bool
}

func (c *cmdProjectDelete) command() *cobra.Command {
Expand All @@ -191,6 +194,7 @@ func (c *cmdProjectDelete) command() *cobra.Command {
cmd.Long = cli.FormatSection(i18n.G("Description"), i18n.G(
`Delete projects`))

cmd.Flags().BoolVarP(&c.flagForce, "force", "f", false, i18n.G("Force delete the project and everything it contains."))
cmd.RunE = c.run

cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
Expand All @@ -204,6 +208,16 @@ func (c *cmdProjectDelete) command() *cobra.Command {
return cmd
}

func (c *cmdProjectDelete) promptConfirmation(name string) bool {
reader := bufio.NewReader(os.Stdin)

fmt.Printf(i18n.G("Remove %s and everything it contains (instances, images, volumes, networks, ...) (yes/no): "), name)
input, _ := reader.ReadString('\n')
input = strings.TrimSuffix(input, "\n")

return slices.Contains([]string{i18n.G("yes")}, strings.ToLower(input))
}

func (c *cmdProjectDelete) run(cmd *cobra.Command, args []string) error {
// Quick checks.
exit, err := c.global.CheckArgs(cmd, args, 1, 1)
Expand All @@ -228,10 +242,22 @@ func (c *cmdProjectDelete) run(cmd *cobra.Command, args []string) error {
return errors.New(i18n.G("Missing project name"))
}

// Delete the project
err = resource.server.DeleteProject(resource.name)
if err != nil {
return err
// Delete the project, server is unable to find the project here.
if c.flagForce {
if !c.promptConfirmation(resource.name) {
fmt.Println(i18n.G("Project deletion aborted"))
return nil
}

err := resource.server.DeleteProjectForce(resource.name)
if err != nil {
return err
}
} else {
err = resource.server.DeleteProject(resource.name)
if err != nil {
return err
}
}

if !c.global.flagQuiet {
Expand Down
Loading
Loading