1
0
mirror of https://github.com/lxc/incus.git synced 2026-02-05 09:46:19 +01:00
Files
incus/cmd/incusd/instance_put.go
2025-05-23 01:46:13 -04:00

242 lines
6.1 KiB
Go

package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"github.com/google/uuid"
"github.com/gorilla/mux"
internalInstance "github.com/lxc/incus/v6/internal/instance"
"github.com/lxc/incus/v6/internal/server/db"
"github.com/lxc/incus/v6/internal/server/db/cluster"
"github.com/lxc/incus/v6/internal/server/db/operationtype"
deviceConfig "github.com/lxc/incus/v6/internal/server/device/config"
"github.com/lxc/incus/v6/internal/server/instance"
"github.com/lxc/incus/v6/internal/server/operations"
projecthelpers "github.com/lxc/incus/v6/internal/server/project"
"github.com/lxc/incus/v6/internal/server/request"
"github.com/lxc/incus/v6/internal/server/response"
"github.com/lxc/incus/v6/internal/server/state"
localUtil "github.com/lxc/incus/v6/internal/server/util"
"github.com/lxc/incus/v6/internal/version"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/osarch"
"github.com/lxc/incus/v6/shared/revert"
)
// swagger:operation PUT /1.0/instances/{name} instances instance_put
//
// Update the instance
//
// Updates the instance configuration or trigger a snapshot restore.
//
// ---
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - in: query
// name: project
// description: Project name
// type: string
// example: default
// - in: body
// name: instance
// description: Update request
// schema:
// $ref: "#/definitions/InstancePut"
// responses:
// "202":
// $ref: "#/responses/Operation"
// "400":
// $ref: "#/responses/BadRequest"
// "403":
// $ref: "#/responses/Forbidden"
// "500":
// $ref: "#/responses/InternalServerError"
func instancePut(d *Daemon, r *http.Request) response.Response {
// Don't mess with instance while in setup mode.
<-d.waitReady.Done()
s := d.State()
projectName := request.ProjectParam(r)
// Get the container
name, err := url.PathUnescape(mux.Vars(r)["name"])
if err != nil {
return response.SmartError(err)
}
if internalInstance.IsSnapshot(name) {
return response.BadRequest(errors.New("Invalid instance name"))
}
// Handle requests targeted to a container on a different node
resp, err := forwardedResponseIfInstanceIsRemote(s, r, projectName, name)
if err != nil {
return response.SmartError(err)
}
if resp != nil {
return resp
}
reverter := revert.New()
defer reverter.Fail()
unlock, err := instanceOperationLock(s.ShutdownCtx, projectName, name)
if err != nil {
return response.SmartError(err)
}
reverter.Add(func() {
unlock()
})
inst, err := instance.LoadByProjectAndName(s, projectName, name)
if err != nil {
return response.SmartError(err)
}
// Validate the ETag
err = localUtil.EtagCheck(r, inst.ETag())
if err != nil {
return response.PreconditionFailed(err)
}
configRaw := api.InstancePut{}
err = json.NewDecoder(r.Body).Decode(&configRaw)
if err != nil {
return response.BadRequest(err)
}
architecture, err := osarch.ArchitectureID(configRaw.Architecture)
if err != nil {
architecture = 0
}
var do func(*operations.Operation) error
var opType operationtype.Type
if configRaw.Restore == "" {
// Check project limits.
apiProfiles := make([]api.Profile, 0, len(configRaw.Profiles))
err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
profiles, err := cluster.GetProfilesIfEnabled(ctx, tx.Tx(), projectName, configRaw.Profiles)
if err != nil {
return err
}
profileConfigs, err := cluster.GetAllProfileConfigs(ctx, tx.Tx())
if err != nil {
return err
}
profileDevices, err := cluster.GetAllProfileDevices(ctx, tx.Tx())
if err != nil {
return err
}
for _, profile := range profiles {
apiProfile, err := profile.ToAPI(ctx, tx.Tx(), profileConfigs, profileDevices)
if err != nil {
return err
}
apiProfiles = append(apiProfiles, *apiProfile)
}
return projecthelpers.AllowInstanceUpdate(tx, projectName, name, configRaw, inst.LocalConfig())
})
if err != nil {
return response.SmartError(err)
}
// Update container configuration
do = func(op *operations.Operation) error {
inst.SetOperation(op)
defer unlock()
args := db.InstanceArgs{
Architecture: architecture,
Config: configRaw.Config,
Description: configRaw.Description,
Devices: deviceConfig.NewDevices(configRaw.Devices),
Ephemeral: configRaw.Ephemeral,
Profiles: apiProfiles,
Project: projectName,
}
err = inst.Update(args, true)
if err != nil {
return err
}
return nil
}
opType = operationtype.InstanceUpdate
} else {
// Snapshot Restore
do = func(op *operations.Operation) error {
defer unlock()
return instanceSnapRestore(s, projectName, name, configRaw.Restore, configRaw.Stateful, op)
}
opType = operationtype.SnapshotRestore
}
resources := map[string][]api.URL{}
resources["instances"] = []api.URL{*api.NewURL().Path(version.APIVersion, "instances", name)}
op, err := operations.OperationCreate(s, projectName, operations.OperationClassTask, opType, resources, nil, do, nil, nil, r)
if err != nil {
return response.InternalError(err)
}
reverter.Success()
return operations.OperationResponse(op)
}
func instanceSnapRestore(s *state.State, projectName string, name string, snap string, stateful bool, op *operations.Operation) error {
// normalize snapshot name
if !internalInstance.IsSnapshot(snap) {
snap = name + internalInstance.SnapshotDelimiter + snap
}
inst, err := instance.LoadByProjectAndName(s, projectName, name)
if err != nil {
return err
}
inst.SetOperation(op)
source, err := instance.LoadByProjectAndName(s, projectName, snap)
if err != nil {
switch {
case response.IsNotFoundError(err):
return fmt.Errorf("Snapshot %s does not exist", snap)
default:
return err
}
}
source.SetOperation(op)
// Generate a new `volatile.uuid.generation` to differentiate this instance restored from a snapshot from the original instance.
source.LocalConfig()["volatile.uuid.generation"] = uuid.New().String()
err = inst.Restore(source, stateful)
if err != nil {
return err
}
return nil
}