package main import ( "cmp" "errors" "fmt" "isle/daemon/daecommon" "slices" "golang.org/x/exp/maps" ) type storageAllocation struct { Index int `yaml:"index"` daecommon.ConfigStorageAllocation `yaml:",inline"` } func indexStorageAllocations( config daecommon.NetworkConfig, ) []storageAllocation { slices.SortFunc( config.Storage.Allocations, func(i, j daecommon.ConfigStorageAllocation) int { return cmp.Compare(i.RPCPort, j.RPCPort) }, ) allocs := make([]storageAllocation, len(config.Storage.Allocations)) for i := range config.Storage.Allocations { allocs[i] = storageAllocation{i, config.Storage.Allocations[i]} } return allocs } var subCmdStorageAllocationAdd = subCmd{ name: "add-allocation", descr: "Adds a new storage allocation to the host", do: func(ctx subCmdCtx) error { var alloc daecommon.ConfigStorageAllocation ctx.flags.StringVar( &alloc.DataPath, "data-path", "", "Path to the directory data should be stored in", ) ctx.flags.StringVar( &alloc.MetaPath, "meta-path", "", "Path to the directory metadata should be stored in. This is a"+ " smaller dataset which benefits from a faster drive, if"+ " possible.", ) ctx.flags.IntVar( &alloc.Capacity, "capacity", 0, "How many gigabytes to allocate.", ) ctx.flags.IntVar( &alloc.S3APIPort, "s3-api-port", 0, "Which port of the VPN network interface to serve the S3 API on."+ " Will be automatically assigned if not given.", ) ctx.flags.IntVar( &alloc.RPCPort, "rpc-port", 0, "Which port of the VPN network interface to serve RPC requests on."+ " Will be automatically assigned if not given. Once this port"+ " is defined for an allocation it cannot be changed.", ) ctx.flags.IntVar( &alloc.AdminPort, "admin-port", 0, "Which port of the VPN network interface to serve admin requests"+ " on. Will be automatically assigned if not given.", ) ctx, err := ctx.withParsedFlags(nil) if err != nil { return fmt.Errorf("parsing flags: %w", err) } if alloc.DataPath == "" || alloc.MetaPath == "" || alloc.Capacity == 0 { return errors.New( "--data-path, --meta-path, and --capacity are required", ) } config, err := ctx.daemonRPC.GetConfig(ctx) if err != nil { return fmt.Errorf("getting network config: %w", err) } config.Storage.Allocations = append(config.Storage.Allocations, alloc) if err := ctx.daemonRPC.SetConfig(ctx, config); err != nil { return fmt.Errorf("updating the network config: %w", err) } return nil }, } var subCmdStorageAllocationList = subCmd{ name: "list-allocation", plural: "s", descr: "Lists all storage which is currently allocated on this host", do: doWithOutput(func(ctx subCmdCtx) (any, error) { ctx, err := ctx.withParsedFlags(nil) if err != nil { return nil, fmt.Errorf("parsing flags: %w", err) } config, err := ctx.daemonRPC.GetConfig(ctx) if err != nil { return nil, fmt.Errorf("getting network config: %w", err) } return indexStorageAllocations(config), nil }), } var subCmdStorageAllocationRemove = subCmd{ name: "remove-allocation", descr: "Removes an allocation which has been previously added. " + "Allocations are identified by their index field from the output of " + "`storage list-allocation(s)`.", do: func(ctx subCmdCtx) error { indexes := ctx.flags.IntSlice( "index", nil, "Index of the storage allocation which should be removed. Can be "+ "specified more than once", ) ctx, err := ctx.withParsedFlags(nil) if err != nil { return fmt.Errorf("parsing flags: %w", err) } if len(*indexes) == 0 { return errors.New("At least one --index must be specified") } config, err := ctx.daemonRPC.GetConfig(ctx) if err != nil { return fmt.Errorf("getting network config: %w", err) } var ( allocs = indexStorageAllocations(config) allocsByIndex = map[int]daecommon.ConfigStorageAllocation{} ) for _, alloc := range allocs { allocsByIndex[alloc.Index] = alloc.ConfigStorageAllocation } for _, index := range *indexes { if _, ok := allocsByIndex[index]; !ok { return fmt.Errorf( "Index %d not found in configured storage allocations: %w", index, err, ) } delete(allocsByIndex, index) } // we sort the new allocation set so that tests are deterministic newAllocs := maps.Values(allocsByIndex) slices.SortFunc( newAllocs, func(i, j daecommon.ConfigStorageAllocation) int { return cmp.Compare(i.RPCPort, j.RPCPort) }, ) config.Storage.Allocations = newAllocs if err := ctx.daemonRPC.SetConfig(ctx, config); err != nil { return fmt.Errorf("updating the network config: %w", err) } return nil }, } var subCmdStorage = subCmd{ name: "storage", descr: "Sub-commands related to configuration of storage on this host", do: func(ctx subCmdCtx) error { return ctx.doSubCmd( subCmdStorageAllocationAdd, subCmdStorageAllocationList, subCmdStorageAllocationRemove, ) }, }