Skip to content

Commit

Permalink
incusd/api_project: Add force deletion logic
Browse files Browse the repository at this point in the history
Closes #245

Signed-off-by: Stéphane Graber <stgraber@stgraber.org>
  • Loading branch information
stgraber committed Jun 27, 2024
1 parent ad228b6 commit b19baa3
Showing 1 changed file with 242 additions and 3 deletions.
245 changes: 242 additions & 3 deletions cmd/incusd/api_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/gorilla/mux"

"github.com/lxc/incus/v6/client"
"github.com/lxc/incus/v6/internal/jmap"
"github.com/lxc/incus/v6/internal/server/auth"
"github.com/lxc/incus/v6/internal/server/db"
Expand Down Expand Up @@ -917,6 +918,7 @@ func projectDelete(d *Daemon, r *http.Request) response.Response {
}

var id int64
var usedBy []string
err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
project, err := cluster.GetProject(ctx, tx.Tx(), name)
if err != nil {
Expand All @@ -933,18 +935,255 @@ func projectDelete(d *Daemon, r *http.Request) response.Response {
return fmt.Errorf("Only empty projects can be removed.")
}
} else {
// WIP: delete everything
logger.Errorf("WIP")
usedBy, err = projectUsedBy(ctx, tx, project)
if err != nil {
return err
}
}

id, err = cluster.GetProjectID(ctx, tx.Tx(), name)
if err != nil {
return fmt.Errorf("Fetch project id %q: %w", name, err)
}

return cluster.DeleteProject(ctx, tx.Tx(), name)
return nil
})
if err != nil {
return response.SmartError(err)
}

// Handle requests to empty the project.
if force {
// Parse used by list.
defaultProfile := api.NewURL().Path(version.APIVersion, "profiles", api.ProjectDefaultName).Project(name).String()
entries := map[string][]string{}
var count int

for _, u := range usedBy {
// Skip the default profile.
if u == defaultProfile {
continue
}

// Parse the URL.
uri, err := url.Parse(u)
if err != nil {
return response.InternalError(err)
}

elements := strings.Split(uri.Path, "/")
if len(elements) < 4 {
return response.InternalError(fmt.Errorf("Bad usedBy entry: %s", u))
}

if elements[2] == "storage-pools" {
if elements[4] == "buckets" {
if entries["storage-buckets"] == nil {
entries["storage-buckets"] = []string{}
}

entry := fmt.Sprintf("%s/%s", elements[3], elements[5])
target := uri.Query().Get("target")
if target != "" {
entry = fmt.Sprintf("%s/%s", entry, target)
}

entries["storage-buckets"] = append(entries["storage-buckets"], entry)
} else if elements[4] == "volumes" {
if entries["storage-volumes"] == nil {
entries["storage-volumes"] = []string{}
}

entry := fmt.Sprintf("%s/%s", elements[3], elements[6])
target := uri.Query().Get("target")
if target != "" {
entry = fmt.Sprintf("%s/%s", entry, target)
}

entries["storage-volumes"] = append(entries["storage-volumes"], entry)
}
} else {
if entries[elements[2]] == nil {
entries[elements[2]] = []string{}
}

entries[elements[2]] = append(entries[elements[2]], elements[3])
}

count++
}

// Connect to the local server.
target, err := incus.ConnectIncusUnix(s.OS.GetUnixSocket(), nil)
if err != nil {
return response.InternalError(err)
}

target = target.UseProject(name)

// Delete instances.
for _, instName := range entries["instances"] {
// Get current instance state.
instState, _, err := target.GetInstance(instName)
if err != nil {
return response.InternalError(err)
}

// If running, force stop it.
if instState.StatusCode != api.Stopped {
req := api.InstanceStatePut{
Action: "stop",
Timeout: -1,
Force: true,
}

op, err := target.UpdateInstanceState(instName, req, "")
if err != nil {
return response.InternalError(err)
}

err = op.Wait()
if err != nil {
return response.InternalError(err)
}
}

// Get the instance configuration.
inst, _, err := target.GetInstance(instName)
if err != nil {
return response.InternalError(err)
}

// Clear security.protection.delete if set.
if util.IsTrue(inst.ExpandedConfig["security.protection.delete"]) {
inst.Config["security.protection.delete"] = "false"
op, err := target.UpdateInstance(instName, inst.Writable(), "")
if err != nil {
return response.InternalError(err)
}

err = op.Wait()
if err != nil {
return response.InternalError(err)
}
}

// Delete the instance.
op, err := target.DeleteInstance(instName)
if err != nil {
return response.InternalError(err)
}

err = op.Wait()
if err != nil {
return response.InternalError(err)
}

// Done deleting the instance.
count--
}

// Delete profiles.
for _, profileName := range entries["profiles"] {
err := target.DeleteProfile(profileName)
if err != nil {
return response.InternalError(err)
}

// Done deleting the profile.
count--
}

// Delete images.
for _, imageFingerprint := range entries["images"] {
op, err := target.DeleteImage(imageFingerprint)
if err != nil {
return response.InternalError(err)
}

err = op.Wait()
if err != nil {
return response.InternalError(err)
}

// Done deleting the image.
count--
}

// Delete networks.
for _, networkName := range entries["networks"] {
err := target.DeleteNetwork(networkName)
if err != nil {
return response.InternalError(err)
}

// Done deleting the network.
count--
}

// Delete network ACLs.
for _, networkACLName := range entries["network-acls"] {
err := target.DeleteNetworkACL(networkACLName)
if err != nil {
return response.InternalError(err)
}

// Done deleting the network ACL.
count--
}

// Delete network zones.
for _, networkZoneName := range entries["network-zones"] {
err := target.DeleteNetworkZone(networkZoneName)
if err != nil {
return response.InternalError(err)
}

// Done deleting the network zone.
count--
}

// Delete storage volumes.
for _, volume := range entries["storage-volumes"] {
fields := strings.Split(volume, "/")
if len(fields) == 3 {
target.UseTarget(fields[2])
}

err := target.DeleteStoragePoolVolume(fields[0], "custom", fields[1])
if err != nil {
return response.InternalError(err)
}

// Done deleting the storage volume.
count--
}

// Delete storage buckets.
for _, volume := range entries["storage-buckets"] {
fields := strings.Split(volume, "/")
if len(fields) == 3 {
target.UseTarget(fields[2])
}

err := target.DeleteStoragePoolBucket(fields[0], fields[1])
if err != nil {
return response.InternalError(err)
}

// Done deleting the storage volume.
count--
}

// Check if anything is left.
if count != 0 {
return response.BadRequest(fmt.Errorf("Project couldn't be automatically emptied"))
}
}

err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
return cluster.DeleteProject(ctx, tx.Tx(), name)
})
if err != nil {
return response.SmartError(err)
}
Expand Down

0 comments on commit b19baa3

Please sign in to comment.