diff --git a/client/incus_server.go b/client/incus_server.go index 164fd0abd22..73268c998aa 100644 --- a/client/incus_server.go +++ b/client/incus_server.go @@ -9,6 +9,8 @@ import ( "github.com/gorilla/websocket" "github.com/lxc/incus/v6/shared/api" + "github.com/lxc/incus/v6/shared/util" + localtls "github.com/lxc/incus/v6/shared/tls" ) @@ -405,6 +407,68 @@ func (r *ProtocolIncus) ApplyServerPreseed(config api.InitPreseed) error { } } + // Apply storage volumes configuration. + applyStorageVolume := func(storageVolume api.InitStorageVolumesProjectPost) error { + // Get the current storageVolume. + currentStorageVolume, etag, err := r.UseProject(storageVolume.Project).GetStoragePoolVolume(storageVolume.Pool, storageVolume.Type, storageVolume.Name) + + if err != nil { + // Create the storage volume if it doesn't exist. + err := r.UseProject(storageVolume.Project).CreateStoragePoolVolume(storageVolume.Pool, storageVolume.StorageVolumesPost) + if err != nil { + return fmt.Errorf("Failed to create storage volume %q in project %q on pool %q: %w", storageVolume.Name, storageVolume.Project, storageVolume.Pool, err) + } + } else { + // Quick check. + if currentStorageVolume.Type != storageVolume.Type { + return fmt.Errorf("Storage volume %q in project %q is of type %q instead of %q", currentStorageVolume.Name, storageVolume.Project, currentStorageVolume.Type, storageVolume.Type) + } + + // Prepare the update. + newStorageVolume := api.StorageVolumePut{} + err = util.DeepCopy(currentStorageVolume.Writable(), &newStorageVolume) + if err != nil { + return fmt.Errorf("Failed to copy configuration of storage volume %q in project %q: %w", storageVolume.Name, storageVolume.Project, err) + } + + // Description override. + if storageVolume.Description != "" { + newStorageVolume.Description = storageVolume.Description + } + + // Config overrides. + for k, v := range storageVolume.Config { + newStorageVolume.Config[k] = fmt.Sprintf("%v", v) + } + + // Apply it. + err = r.UseProject(storageVolume.Project).UpdateStoragePoolVolume(storageVolume.Pool, storageVolume.Type, currentStorageVolume.Name, newStorageVolume, etag) + if err != nil { + return fmt.Errorf("Failed to update storage volume %q in project %q: %w", storageVolume.Name, storageVolume.Project, err) + } + } + + return nil + } + + // Apply storage volumes in the default project before other projects config. + for i := range config.Server.StorageVolumes { + // Populate default project if not specified. + if config.Server.StorageVolumes[i].Project == "" { + config.Server.StorageVolumes[i].Project = api.ProjectDefaultName + } + // Populate default type if not specified. + if config.Server.StorageVolumes[i].Type == "" { + config.Server.StorageVolumes[i].Type = "custom" + } + + err := applyStorageVolume(config.Server.StorageVolumes[i]) + if err != nil { + return err + } + + } + // Apply profile configuration. if config.Server.Profiles != nil && len(config.Server.Profiles) > 0 { // Get the list of profiles. diff --git a/doc/api-extensions.md b/doc/api-extensions.md index ec44098f11a..a6a6a223233 100644 --- a/doc/api-extensions.md +++ b/doc/api-extensions.md @@ -2689,3 +2689,6 @@ This extends the QEMU scriptlet feature by allowing to modify QEMU configuration ## `network_bridge_acl_devices` This adds support for device ACLs when attached to a bridged network. + +## `init_preseed_storage_volumes` +This API extension provides the ability to configure storage volumes in preseed init. diff --git a/doc/howto/initialize.md b/doc/howto/initialize.md index ca8e854365d..9bb76b34493 100644 --- a/doc/howto/initialize.md +++ b/doc/howto/initialize.md @@ -152,6 +152,11 @@ storage_pools: config: source: my-zfs-pool/my-zfs-dataset +# Storage volumes +storage_volumes: +- name: my-vol + pool: data + # Network devices networks: - name: incus-my-bridge diff --git a/doc/rest-api.yaml b/doc/rest-api.yaml index 4433f95c0da..0bf48a653a9 100644 --- a/doc/rest-api.yaml +++ b/doc/rest-api.yaml @@ -1271,6 +1271,13 @@ definitions: $ref: '#/definitions/StoragePoolsPost' type: array x-go-name: StoragePools + storage_volumes: + description: Storage Volumes to add + example: local dir storage volume + items: + $ref: '#/definitions/InitStorageVolumesProjectPost' + type: array + x-go-name: StorageVolumes title: InitLocalPreseed represents initialization configuration. type: object x-go-package: github.com/lxc/incus/v6/shared/api @@ -1317,6 +1324,55 @@ definitions: title: InitPreseed represents initialization configuration that can be supplied to `init`. type: object x-go-package: github.com/lxc/incus/v6/shared/api + InitStorageVolumesProjectPost: + properties: + Pool: + description: Storage pool in which the volume will reside + example: '"default"' + type: string + Project: + description: Project in which the volume will reside + example: '"default"' + type: string + config: + additionalProperties: + type: string + description: Storage volume configuration map (refer to doc/storage.md) + example: + size: 50GiB + zfs.remove_snapshots: "true" + type: object + x-go-name: Config + content_type: + description: Volume content type (filesystem or block) + example: filesystem + type: string + x-go-name: ContentType + description: + description: Description of the storage volume + example: My custom volume + type: string + x-go-name: Description + name: + description: Volume name + example: foo + type: string + x-go-name: Name + restore: + description: Name of a snapshot to restore + example: snap0 + type: string + x-go-name: Restore + source: + $ref: '#/definitions/StorageVolumeSource' + type: + description: Volume type (container, custom, image or virtual-machine) + example: custom + type: string + x-go-name: Type + title: InitStorageVolumesProjectPost represents the fields of a new storage volume along with its associated pool. + type: object + x-go-package: github.com/lxc/incus/v6/shared/api Instance: properties: architecture: diff --git a/internal/version/api.go b/internal/version/api.go index a28a0986acb..e676dca04c2 100644 --- a/internal/version/api.go +++ b/internal/version/api.go @@ -458,6 +458,7 @@ var APIExtensions = []string{ "console_force", "network_ovn_state_addresses", "network_bridge_acl_devices", + "init_preseed_storage_volumes", } // APIExtensionsCount returns the number of available API extensions. diff --git a/shared/api/init.go b/shared/api/init.go index b983de8956c..2f56569fa42 100644 --- a/shared/api/init.go +++ b/shared/api/init.go @@ -26,6 +26,10 @@ type InitLocalPreseed struct { // Example: local dir storage pool StoragePools []StoragePoolsPost `json:"storage_pools" yaml:"storage_pools"` + // Storage Volumes to add + // Example: local dir storage volume + StorageVolumes []InitStorageVolumesProjectPost `json:"storage_volumes" yaml:"storage_volumes"` + // Profiles to add // Example: "default" profile with a root disk device Profiles []ProfilesPost `json:"profiles" yaml:"profiles"` @@ -48,6 +52,21 @@ type InitNetworksProjectPost struct { Project string } +// InitStorageVolumesProjectPost represents the fields of a new storage volume along with its associated pool. +// +// swagger:model +// +// API extension: init_preseed_storage_volumes. +type InitStorageVolumesProjectPost struct { + StorageVolumesPost `yaml:",inline"` + // Storage pool in which the volume will reside + // Example: "default" + Pool string + // Project in which the volume will reside + // Example: "default" + Project string +} + // InitClusterPreseed represents initialization configuration for the cluster. // // swagger:model diff --git a/test/suites/init_preseed.sh b/test/suites/init_preseed.sh index e501cb5f311..bf00006a0a1 100644 --- a/test/suites/init_preseed.sh +++ b/test/suites/init_preseed.sh @@ -11,6 +11,7 @@ test_init_preseed() { INCUS_DIR=${INCUS_INIT_DIR} storage_pool="incustest-$(basename "${INCUS_DIR}")-data" + storage_volume="${storage_pool}-volume" # In case we're running against the ZFS backend, let's test # creating a zfs storage pool, otherwise just use dir. if [ "$incus_backend" = "zfs" ]; then @@ -36,6 +37,9 @@ storage_pools: driver: $driver config: source: $source +storage_volumes: +- name: ${storage_volume} + pool: ${storage_pool} networks: - name: inct$$ type: bridge @@ -66,6 +70,7 @@ EOF incus network list | grep -q "inct$$" incus storage list | grep -q "${storage_pool}" incus storage show "${storage_pool}" | grep -q "$source" + incus storage volume list "${storage_pool}" | grep -q "${storage_volume}" incus profile list | grep -q "test-profile" incus profile show default | grep -q "pool: ${storage_pool}" incus profile show test-profile | grep -q "limits.memory: 2GiB" @@ -74,6 +79,7 @@ EOF printf 'config: {}\ndevices: {}' | incus profile edit default incus profile delete test-profile incus network delete inct$$ + incus storage volume delete "${storage_pool}" "${storage_volume}" incus storage delete "${storage_pool}" if [ "$incus_backend" = "zfs" ]; then