diff --git a/api/storage/storage.proto b/api/storage/storage.proto index 1313c52b86..d41bf7c0f6 100644 --- a/api/storage/storage.proto +++ b/api/storage/storage.proto @@ -23,6 +23,25 @@ message Disk { string model = 2; // DeviceName indicates the disk name (e.g. `sda`). string device_name = 3; + // Name as in `/sys/block//device/name`. + string name = 4; + // Serial as in `/sys/block//device/serial`. + string serial = 5; + // Modalias as in `/sys/block//device/modalias`. + string modalias = 6; + // Uuid as in `/sys/block//device/uuid`. + string uuid = 7; + // Wwid as in `/sys/block//device/wwid`. + string wwid = 8; + enum DiskType { + UNKNOWN = 0; + SSD = 1; + HDD = 2; + NVME = 3; + SD = 4; + } + // Type is a type of the disk: nvme, ssd, hdd, sd card. + DiskType type = 9; } // DisksResponse represents the response of the `Disks` RPC. diff --git a/cmd/talosctl/cmd/mgmt/validate.go b/cmd/talosctl/cmd/mgmt/validate.go index 2680c678b8..99138f6f0a 100644 --- a/cmd/talosctl/cmd/mgmt/validate.go +++ b/cmd/talosctl/cmd/mgmt/validate.go @@ -11,6 +11,7 @@ import ( "github.com/talos-systems/talos/internal/app/machined/pkg/runtime" "github.com/talos-systems/talos/pkg/cli" + "github.com/talos-systems/talos/pkg/machinery/config" "github.com/talos-systems/talos/pkg/machinery/config/configloader" ) @@ -26,7 +27,7 @@ var validateCmd = &cobra.Command{ Long: ``, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - config, err := configloader.NewFromFile(validateConfigArg) + cfg, err := configloader.NewFromFile(validateConfigArg) if err != nil { return err } @@ -35,7 +36,7 @@ var validateCmd = &cobra.Command{ if err != nil { return err } - if err := config.Validate(mode); err != nil { + if err := cfg.Validate(mode, config.WithLocal()); err != nil { return err } diff --git a/cmd/talosctl/cmd/talos/disks.go b/cmd/talosctl/cmd/talos/disks.go new file mode 100644 index 0000000000..2699960b60 --- /dev/null +++ b/cmd/talosctl/cmd/talos/disks.go @@ -0,0 +1,132 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package talos + +import ( + "context" + "crypto/tls" + "fmt" + "os" + "strings" + "text/tabwriter" + + humanize "github.com/dustin/go-humanize" + "github.com/spf13/cobra" + + "github.com/talos-systems/talos/pkg/cli" + "github.com/talos-systems/talos/pkg/machinery/client" +) + +var disksCmdFlags struct { + insecure bool +} + +var disksCmd = &cobra.Command{ + Use: "disks", + Short: "Get the list of disks from /sys/block on the machine", + Long: ``, + RunE: func(cmd *cobra.Command, args []string) error { + if disksCmdFlags.insecure { + ctx := context.Background() + + c, err := client.New(ctx, client.WithTLSConfig(&tls.Config{ + InsecureSkipVerify: true, + }), client.WithEndpoints(Nodes...)) + if err != nil { + return err + } + + return printDisks(ctx, c) + } + + return WithClient(printDisks) + }, +} + +//nolint:gocyclo +func printDisks(ctx context.Context, c *client.Client) error { + response, err := c.Disks(ctx) + if err != nil { + if response == nil { + return fmt.Errorf("error getting disks: %w", err) + } + + cli.Warning("%s", err) + } + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + node := "" + + labels := strings.Join( + []string{ + "DEV", + "MODEL", + "SERIAL", + "TYPE", + "UUID", + "WWID", + "MODALIAS", + "NAME", + "SIZE", + }, "\t") + + getWithPlaceholder := func(in string) string { + if in == "" { + return "-" + } + + return in + } + + for i, message := range response.Messages { + if message.Metadata != nil && message.Metadata.Hostname != "" { + node = message.Metadata.Hostname + } + + if len(message.Disks) == 0 { + continue + } + + for j, disk := range message.Disks { + if i == 0 && j == 0 { + if node != "" { + fmt.Fprintln(w, "NODE\t"+labels) + } else { + fmt.Fprintln(w, labels) + } + } + + args := []interface{}{} + + if node != "" { + args = append(args, node) + } + + args = append(args, []interface{}{ + getWithPlaceholder(disk.DeviceName), + getWithPlaceholder(disk.Model), + getWithPlaceholder(disk.Serial), + disk.Type.String(), + getWithPlaceholder(disk.Uuid), + getWithPlaceholder(disk.Wwid), + getWithPlaceholder(disk.Modalias), + getWithPlaceholder(disk.Name), + humanize.Bytes(disk.Size), + }...) + + pattern := strings.Repeat("%s\t", len(args)) + pattern = strings.TrimSpace(pattern) + "\n" + + fmt.Fprintf(w, pattern, args...) + } + } + + return w.Flush() +} + +func init() { + disksCmd.Flags().BoolVarP(&disksCmdFlags.insecure, "insecure", "i", false, "get disks using the insecure (encrypted with no auth) maintenance service") + addCommand(disksCmd) +} diff --git a/go.mod b/go.mod index bac185c991..800fc03a76 100644 --- a/go.mod +++ b/go.mod @@ -68,7 +68,7 @@ require ( github.com/stretchr/testify v1.7.0 github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 // can't be bumped, requires new runc github.com/talos-systems/crypto v0.2.1-0.20210202170911-39584f1b6e54 - github.com/talos-systems/go-blockdevice v0.2.1-0.20210305142622-bb3ad73f6983 + github.com/talos-systems/go-blockdevice v0.2.1-0.20210322192639-776b37d31de0 github.com/talos-systems/go-cmd v0.0.0-20210216164758-68eb0067e0f0 github.com/talos-systems/go-loadbalancer v0.1.0 github.com/talos-systems/go-procfs v0.0.0-20210108152626-8cbc42d3dc24 diff --git a/go.sum b/go.sum index c084a0368d..dfee42a4ce 100644 --- a/go.sum +++ b/go.sum @@ -893,6 +893,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= @@ -974,8 +976,8 @@ github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0 github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/talos-systems/crypto v0.2.1-0.20210202170911-39584f1b6e54 h1:2IGs3f0qG7f33Fv37XuYzIMxLARl4dcpwahZXLmdT2A= github.com/talos-systems/crypto v0.2.1-0.20210202170911-39584f1b6e54/go.mod h1:OXCK52Q0dzm88YRG4VdTBdidkPUtqrCxCyW7bUs4DAw= -github.com/talos-systems/go-blockdevice v0.2.1-0.20210305142622-bb3ad73f6983 h1:tYxXpKNUNvrBKFE4itbU3lJVBws/XUmlYkuEI93kCAE= -github.com/talos-systems/go-blockdevice v0.2.1-0.20210305142622-bb3ad73f6983/go.mod h1:Wt0/JMsa+WysYRDlNo6fu9rdn/re73uRnktFWyVUqjs= +github.com/talos-systems/go-blockdevice v0.2.1-0.20210322192639-776b37d31de0 h1:ZOIwpHUR0i2e4VaCe8nwHZuBRjD25XUv+xImyPWHw50= +github.com/talos-systems/go-blockdevice v0.2.1-0.20210322192639-776b37d31de0/go.mod h1:qnn/zDc09I1DA2BUDDCOSA2D0P8pIDjN8pGiRoRaQig= github.com/talos-systems/go-cmd v0.0.0-20210216164758-68eb0067e0f0 h1:DI+BjK+fcrLBc70Fi50dZocQcaHosqsuWHrGHKp2NzE= github.com/talos-systems/go-cmd v0.0.0-20210216164758-68eb0067e0f0/go.mod h1:kf+rZzTEmlDiYQ6ulslvRONnKLQH8x83TowltGMhO+k= github.com/talos-systems/go-loadbalancer v0.1.0 h1:MQFONvSjoleU8RrKq1O1Z8CyTCJGd4SLqdAHDlR6o9s= diff --git a/hack/docgen/main.go b/hack/docgen/main.go index c6bac56689..9360e85187 100644 --- a/hack/docgen/main.go +++ b/hack/docgen/main.go @@ -319,6 +319,10 @@ func collectFields(s *structType) (fields []*Field) { log.Fatalf("field %q is missing a documentation", f.Names[0].Name) } + if strings.Contains(f.Doc.Text(), "docgen:nodoc") { + continue + } + name := f.Names[0].Name fieldType := formatFieldType(f.Type) diff --git a/hack/release.toml b/hack/release.toml index 920d04429e..5f3360ce0c 100644 --- a/hack/release.toml +++ b/hack/release.toml @@ -6,7 +6,7 @@ github_repo = "talos-systems/talos" match_deps = "^github.com/(talos-systems/[a-zA-Z0-9-]+)$" # previous release -previous = "v0.9.0-beta.0" +previous = "v0.9.0" pre_release = true @@ -24,8 +24,25 @@ preface = """\ [notes.optimize] title = "Optmizations" - descriptions = """\ + description = """\ * Talos `system` services now run without container images on initramfs from the single executable; this change reduces RAM usage, initramfs size and boot time.. +""" + + [notes.machineconfig] + title = "Install Disk Selector" + description = """\ +Install section of the machine config now has `diskSelector` field that allows querying install disk using the list of qualifiers: + +```yaml +... + install: + diskSelector: + size: >= 500GB + model: WDC* +... +``` + +`talosctl disks -n -i` can be used to check allowed disk qualifiers when the node is running in the maintenance mode. """ [make_deps] diff --git a/internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go b/internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go index 1304606b6f..8c2c960794 100644 --- a/internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go +++ b/internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go @@ -41,10 +41,12 @@ import ( installer "github.com/talos-systems/talos/cmd/installer/pkg/install" "github.com/talos-systems/talos/internal/app/machined/internal/install" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime" + "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/disk" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/adv" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub" "github.com/talos-systems/talos/internal/app/machined/pkg/system" + storaged "github.com/talos-systems/talos/internal/app/storaged" "github.com/talos-systems/talos/internal/pkg/configuration" "github.com/talos-systems/talos/internal/pkg/containers" taloscontainerd "github.com/talos-systems/talos/internal/pkg/containers/containerd" @@ -62,6 +64,7 @@ import ( "github.com/talos-systems/talos/pkg/machinery/api/inspect" "github.com/talos-systems/talos/pkg/machinery/api/machine" "github.com/talos-systems/talos/pkg/machinery/api/resource" + "github.com/talos-systems/talos/pkg/machinery/api/storage" "github.com/talos-systems/talos/pkg/machinery/config" machinetype "github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/machine" "github.com/talos-systems/talos/pkg/machinery/constants" @@ -106,6 +109,7 @@ func (s *Server) Register(obj *grpc.Server) { cluster.RegisterClusterServiceServer(obj, s) resource.RegisterResourceServiceServer(obj, &ResourceServer{server: s}) inspect.RegisterInspectServiceServer(obj, &InspectServer{server: s}) + storage.RegisterStorageServiceServer(obj, &storaged.Server{}) } // ApplyConfiguration implements machine.MachineService. @@ -246,8 +250,13 @@ func (s *Server) Rollback(ctx context.Context, in *machine.RollbackRequest) (*ma } }() + disk := s.Controller.Runtime().State().Machine().Disk(disk.WithPartitionLabel(constants.BootPartitionLabel)) + if disk == nil { + return fmt.Errorf("boot disk not found") + } + grub := &grub.Grub{ - BootDisk: s.Controller.Runtime().Config().Machine().Install().Disk(), + BootDisk: disk.Device().Name(), } _, next, err := grub.Labels() diff --git a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go index 39a12314bd..10c563865f 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go @@ -740,10 +740,16 @@ func VerifyInstallation(seq runtime.Sequence, data interface{}) (runtime.TaskExe var ( current string next string + disk string ) + disk, err = r.Config().Machine().Install().Disk() + if err != nil { + return err + } + grub := &grub.Grub{ - BootDisk: r.Config().Machine().Install().Disk(), + BootDisk: disk, } current, next, err = grub.Labels() @@ -1630,8 +1636,15 @@ func Install(seq runtime.Sequence, data interface{}) (runtime.TaskExecutionFunc, installerImage = images.DefaultInstallerImage } + var disk string + + disk, err = r.Config().Machine().Install().Disk() + if err != nil { + return err + } + err = install.RunInstallerContainer( - r.Config().Machine().Install().Disk(), + disk, r.State().Platform().Name(), installerImage, r.Config().Machine().Registries(), diff --git a/internal/app/maintenance/server/server.go b/internal/app/maintenance/server/server.go index 2edd002698..293cfef2e0 100644 --- a/internal/app/maintenance/server/server.go +++ b/internal/app/maintenance/server/server.go @@ -25,15 +25,13 @@ import ( // Server implements machine.MachineService, network.NetworkService, and storage.StorageService. type Server struct { - storage.UnimplementedStorageServiceServer machine.UnimplementedMachineServiceServer network.UnimplementedNetworkServiceServer - runtime runtime.Runtime - logger *log.Logger - cfgCh chan []byte - server *grpc.Server - storaged storaged.Server + runtime runtime.Runtime + logger *log.Logger + cfgCh chan []byte + server *grpc.Server } // New initializes and returns a `Server`. @@ -49,16 +47,11 @@ func New(r runtime.Runtime, logger *log.Logger, cfgCh chan []byte) *Server { func (s *Server) Register(obj *grpc.Server) { s.server = obj - storage.RegisterStorageServiceServer(obj, s) + storage.RegisterStorageServiceServer(obj, &storaged.Server{}) machine.RegisterMachineServiceServer(obj, s) network.RegisterNetworkServiceServer(obj, s) } -// Disks implements storage.StorageService. -func (s *Server) Disks(ctx context.Context, in *empty.Empty) (reply *storage.DisksResponse, err error) { - return s.storaged.Disks(ctx, in) -} - // ApplyConfiguration implements machine.MachineService. func (s *Server) ApplyConfiguration(ctx context.Context, in *machine.ApplyConfigurationRequest) (reply *machine.ApplyConfigurationResponse, err error) { if in.OnReboot { diff --git a/internal/app/routerd/main.go b/internal/app/routerd/main.go index 74d8249cce..62b6e924db 100644 --- a/internal/app/routerd/main.go +++ b/internal/app/routerd/main.go @@ -36,6 +36,7 @@ func Main() { router.RegisterLocalBackend("time.TimeService", backend.NewLocal("timed", constants.TimeSocketPath)) router.RegisterLocalBackend("network.NetworkService", backend.NewLocal("networkd", constants.NetworkSocketPath)) router.RegisterLocalBackend("cluster.ClusterService", machinedBackend) + router.RegisterLocalBackend("storage.StorageService", machinedBackend) err := factory.ListenAndServe( router, diff --git a/internal/app/storaged/server.go b/internal/app/storaged/server.go index fb227ee52d..aa41f3d2d8 100644 --- a/internal/app/storaged/server.go +++ b/internal/app/storaged/server.go @@ -8,18 +8,20 @@ import ( "context" "github.com/golang/protobuf/ptypes/empty" - "github.com/talos-systems/go-blockdevice/blockdevice/util" + "github.com/talos-systems/go-blockdevice/blockdevice/util/disk" "github.com/talos-systems/talos/pkg/machinery/api/storage" ) // Server implements storage.StorageService. // TODO: this is not a full blown service yet, it's used as the common base in the machine and the maintenance services. -type Server struct{} +type Server struct { + storage.UnimplementedStorageServiceServer +} // Disks implements storage.StorageService. func (s *Server) Disks(ctx context.Context, in *empty.Empty) (reply *storage.DisksResponse, err error) { - disks, err := util.GetDisks() + disks, err := disk.List() if err != nil { return nil, err } @@ -31,6 +33,10 @@ func (s *Server) Disks(ctx context.Context, in *empty.Empty) (reply *storage.Dis DeviceName: disk.DeviceName, Model: disk.Model, Size: disk.Size, + Name: disk.Name, + Serial: disk.Serial, + Modalias: disk.Modalias, + Type: storage.Disk_DiskType(disk.Type), } } diff --git a/internal/integration/api/generate-config.go b/internal/integration/api/generate-config.go index 2cbafbd87e..7adfca4350 100644 --- a/internal/integration/api/generate-config.go +++ b/internal/integration/api/generate-config.go @@ -95,6 +95,9 @@ func (suite *GenerateConfigSuite) TestGenerate() { suite.Require().NoError(err) + disk, err := config.Machine().Install().Disk() + suite.Require().NoError(err) + suite.Require().EqualValues(request.MachineConfig.Type, config.Machine().Type()) suite.Require().EqualValues(request.ConfigVersion, config.Version()) suite.Require().EqualValues(request.ClusterConfig.Name, config.Cluster().Name()) @@ -103,7 +106,7 @@ func (suite *GenerateConfigSuite) TestGenerate() { suite.Require().EqualValues(request.ClusterConfig.ClusterNetwork.CniConfig.Name, config.Cluster().Network().CNI().Name()) suite.Require().EqualValues(request.ClusterConfig.ClusterNetwork.CniConfig.Urls, config.Cluster().Network().CNI().URLs()) suite.Require().EqualValues(fmt.Sprintf("%s:v%s", constants.KubeletImage, request.MachineConfig.KubernetesVersion), config.Machine().Kubelet().Image()) - suite.Require().EqualValues(request.MachineConfig.InstallConfig.InstallDisk, config.Machine().Install().Disk()) + suite.Require().EqualValues(request.MachineConfig.InstallConfig.InstallDisk, disk) suite.Require().EqualValues(request.MachineConfig.InstallConfig.InstallImage, config.Machine().Install().Image()) suite.Require().EqualValues(request.MachineConfig.NetworkConfig.Hostname, config.Machine().Network().Hostname()) suite.Require().EqualValues(request.MachineConfig.NetworkConfig.Hostname, config.Machine().Network().Hostname()) @@ -138,13 +141,16 @@ func (suite *GenerateConfigSuite) TestGenerate() { suite.Require().NoError(err) + disk, err = config.Machine().Install().Disk() + suite.Require().NoError(err) + suite.Require().EqualValues(request.MachineConfig.Type, joinedConfig.Machine().Type()) suite.Require().EqualValues(request.ConfigVersion, joinedConfig.Version()) suite.Require().EqualValues(request.ClusterConfig.Name, joinedConfig.Cluster().Name()) suite.Require().EqualValues(request.ClusterConfig.ControlPlane.Endpoint, joinedConfig.Cluster().Endpoint().String()) suite.Require().EqualValues(request.ClusterConfig.ClusterNetwork.DnsDomain, joinedConfig.Cluster().Network().DNSDomain()) suite.Require().EqualValues(fmt.Sprintf("%s:v%s", constants.KubeletImage, request.MachineConfig.KubernetesVersion), joinedConfig.Machine().Kubelet().Image()) - suite.Require().EqualValues(request.MachineConfig.InstallConfig.InstallDisk, joinedConfig.Machine().Install().Disk()) + suite.Require().EqualValues(request.MachineConfig.InstallConfig.InstallDisk, disk) suite.Require().EqualValues(request.MachineConfig.InstallConfig.InstallImage, joinedConfig.Machine().Install().Image()) suite.Require().EqualValues(request.MachineConfig.NetworkConfig.Hostname, joinedConfig.Machine().Network().Hostname()) diff --git a/internal/integration/cli/disk.go b/internal/integration/cli/disk.go new file mode 100644 index 0000000000..588797c1e5 --- /dev/null +++ b/internal/integration/cli/disk.go @@ -0,0 +1,30 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// +build integration_cli + +package cli + +import ( + "github.com/talos-systems/talos/internal/integration/base" +) + +// DisksSuite verifies dmesg command. +type DisksSuite struct { + base.CLISuite +} + +// SuiteName ... +func (suite *DisksSuite) SuiteName() string { + return "cli.DisksSuite" +} + +// TestSuccess runs comand with success. +func (suite *DisksSuite) TestSuccess() { + suite.RunCLI([]string{"disks", "--nodes", suite.RandomDiscoveredNode()}) +} + +func init() { + allSuites = append(allSuites, new(DisksSuite)) +} diff --git a/pkg/machinery/api/storage/storage.pb.go b/pkg/machinery/api/storage/storage.pb.go index bea6882660..e859e12a9f 100644 --- a/pkg/machinery/api/storage/storage.pb.go +++ b/pkg/machinery/api/storage/storage.pb.go @@ -24,6 +24,61 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type Disk_DiskType int32 + +const ( + Disk_UNKNOWN Disk_DiskType = 0 + Disk_SSD Disk_DiskType = 1 + Disk_HDD Disk_DiskType = 2 + Disk_NVME Disk_DiskType = 3 + Disk_SD Disk_DiskType = 4 +) + +// Enum value maps for Disk_DiskType. +var ( + Disk_DiskType_name = map[int32]string{ + 0: "UNKNOWN", + 1: "SSD", + 2: "HDD", + 3: "NVME", + 4: "SD", + } + Disk_DiskType_value = map[string]int32{ + "UNKNOWN": 0, + "SSD": 1, + "HDD": 2, + "NVME": 3, + "SD": 4, + } +) + +func (x Disk_DiskType) Enum() *Disk_DiskType { + p := new(Disk_DiskType) + *p = x + return p +} + +func (x Disk_DiskType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Disk_DiskType) Descriptor() protoreflect.EnumDescriptor { + return file_storage_storage_proto_enumTypes[0].Descriptor() +} + +func (Disk_DiskType) Type() protoreflect.EnumType { + return &file_storage_storage_proto_enumTypes[0] +} + +func (x Disk_DiskType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Disk_DiskType.Descriptor instead. +func (Disk_DiskType) EnumDescriptor() ([]byte, []int) { + return file_storage_storage_proto_rawDescGZIP(), []int{0, 0} +} + // Disk represents a disk. type Disk struct { state protoimpl.MessageState @@ -36,6 +91,18 @@ type Disk struct { Model string `protobuf:"bytes,2,opt,name=model,proto3" json:"model,omitempty"` // DeviceName indicates the disk name (e.g. `sda`). DeviceName string `protobuf:"bytes,3,opt,name=device_name,json=deviceName,proto3" json:"device_name,omitempty"` + // Name as in `/sys/block//device/name`. + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + // Serial as in `/sys/block//device/serial`. + Serial string `protobuf:"bytes,5,opt,name=serial,proto3" json:"serial,omitempty"` + // Modalias as in `/sys/block//device/modalias`. + Modalias string `protobuf:"bytes,6,opt,name=modalias,proto3" json:"modalias,omitempty"` + // Uuid as in `/sys/block//device/uuid`. + Uuid string `protobuf:"bytes,7,opt,name=uuid,proto3" json:"uuid,omitempty"` + // Wwid as in `/sys/block//device/wwid`. + Wwid string `protobuf:"bytes,8,opt,name=wwid,proto3" json:"wwid,omitempty"` + // Type is a type of the disk: nvme, ssd, hdd, sd card. + Type Disk_DiskType `protobuf:"varint,9,opt,name=type,proto3,enum=storage.Disk_DiskType" json:"type,omitempty"` } func (x *Disk) Reset() { @@ -91,6 +158,48 @@ func (x *Disk) GetDeviceName() string { return "" } +func (x *Disk) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Disk) GetSerial() string { + if x != nil { + return x.Serial + } + return "" +} + +func (x *Disk) GetModalias() string { + if x != nil { + return x.Modalias + } + return "" +} + +func (x *Disk) GetUuid() string { + if x != nil { + return x.Uuid + } + return "" +} + +func (x *Disk) GetWwid() string { + if x != nil { + return x.Wwid + } + return "" +} + +func (x *Disk) GetType() Disk_DiskType { + if x != nil { + return x.Type + } + return Disk_UNKNOWN +} + // DisksResponse represents the response of the `Disks` RPC. type Disks struct { state protoimpl.MessageState @@ -202,32 +311,46 @@ var file_storage_storage_proto_rawDesc = []byte{ 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x13, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x22, 0x51, 0x0a, 0x04, 0x44, 0x69, 0x73, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, - 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, - 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x5a, 0x0a, 0x05, 0x44, 0x69, 0x73, 0x6b, 0x73, 0x12, 0x2c, - 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x10, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x05, - 0x64, 0x69, 0x73, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x73, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x52, 0x05, 0x64, 0x69, 0x73, 0x6b, - 0x73, 0x22, 0x3b, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x44, - 0x69, 0x73, 0x6b, 0x73, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x32, 0x49, - 0x0a, 0x0e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x37, 0x0a, 0x05, 0x44, 0x69, 0x73, 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x16, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x44, 0x69, 0x73, 0x6b, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x59, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, - 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x42, 0x0a, 0x53, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x41, 0x70, 0x69, 0x50, 0x01, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2d, 0x73, 0x79, 0x73, - 0x74, 0x65, 0x6d, 0x73, 0x2f, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x6d, - 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x72, 0x79, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x74, 0x6f, - 0x72, 0x61, 0x67, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x6f, 0x22, 0xaa, 0x02, 0x0a, 0x04, 0x44, 0x69, 0x73, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x73, + 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, + 0x61, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x12, + 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, + 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x77, 0x77, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x77, 0x77, 0x69, 0x64, 0x12, 0x2a, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x44, + 0x69, 0x73, 0x6b, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x22, 0x3b, 0x0a, 0x08, 0x44, 0x69, 0x73, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, + 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x53, + 0x53, 0x44, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x48, 0x44, 0x44, 0x10, 0x02, 0x12, 0x08, 0x0a, + 0x04, 0x4e, 0x56, 0x4d, 0x45, 0x10, 0x03, 0x12, 0x06, 0x0a, 0x02, 0x53, 0x44, 0x10, 0x04, 0x22, + 0x5a, 0x0a, 0x05, 0x44, 0x69, 0x73, 0x6b, 0x73, 0x12, 0x2c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x05, 0x64, 0x69, 0x73, 0x6b, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, + 0x44, 0x69, 0x73, 0x6b, 0x52, 0x05, 0x64, 0x69, 0x73, 0x6b, 0x73, 0x22, 0x3b, 0x0a, 0x0d, 0x44, + 0x69, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x08, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x73, 0x52, 0x08, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x32, 0x49, 0x0a, 0x0e, 0x53, 0x74, 0x6f, 0x72, + 0x61, 0x67, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x44, 0x69, + 0x73, 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x73, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x42, 0x59, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x42, 0x0a, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x41, + 0x70, 0x69, 0x50, 0x01, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2d, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x2f, 0x74, + 0x61, 0x6c, 0x6f, 0x73, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, + 0x72, 0x79, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -243,27 +366,30 @@ func file_storage_storage_proto_rawDescGZIP() []byte { } var ( - file_storage_storage_proto_msgTypes = make([]protoimpl.MessageInfo, 3) - file_storage_storage_proto_goTypes = []interface{}{ - (*Disk)(nil), // 0: storage.Disk - (*Disks)(nil), // 1: storage.Disks - (*DisksResponse)(nil), // 2: storage.DisksResponse - (*common.Metadata)(nil), // 3: common.Metadata - (*emptypb.Empty)(nil), // 4: google.protobuf.Empty + file_storage_storage_proto_enumTypes = make([]protoimpl.EnumInfo, 1) + file_storage_storage_proto_msgTypes = make([]protoimpl.MessageInfo, 3) + file_storage_storage_proto_goTypes = []interface{}{ + (Disk_DiskType)(0), // 0: storage.Disk.DiskType + (*Disk)(nil), // 1: storage.Disk + (*Disks)(nil), // 2: storage.Disks + (*DisksResponse)(nil), // 3: storage.DisksResponse + (*common.Metadata)(nil), // 4: common.Metadata + (*emptypb.Empty)(nil), // 5: google.protobuf.Empty } ) var file_storage_storage_proto_depIdxs = []int32{ - 3, // 0: storage.Disks.metadata:type_name -> common.Metadata - 0, // 1: storage.Disks.disks:type_name -> storage.Disk - 1, // 2: storage.DisksResponse.messages:type_name -> storage.Disks - 4, // 3: storage.StorageService.Disks:input_type -> google.protobuf.Empty - 2, // 4: storage.StorageService.Disks:output_type -> storage.DisksResponse - 4, // [4:5] is the sub-list for method output_type - 3, // [3:4] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 0, // 0: storage.Disk.type:type_name -> storage.Disk.DiskType + 4, // 1: storage.Disks.metadata:type_name -> common.Metadata + 1, // 2: storage.Disks.disks:type_name -> storage.Disk + 2, // 3: storage.DisksResponse.messages:type_name -> storage.Disks + 5, // 4: storage.StorageService.Disks:input_type -> google.protobuf.Empty + 3, // 5: storage.StorageService.Disks:output_type -> storage.DisksResponse + 5, // [5:6] is the sub-list for method output_type + 4, // [4:5] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_storage_storage_proto_init() } @@ -314,13 +440,14 @@ func file_storage_storage_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_storage_storage_proto_rawDesc, - NumEnums: 0, + NumEnums: 1, NumMessages: 3, NumExtensions: 0, NumServices: 1, }, GoTypes: file_storage_storage_proto_goTypes, DependencyIndexes: file_storage_storage_proto_depIdxs, + EnumInfos: file_storage_storage_proto_enumTypes, MessageInfos: file_storage_storage_proto_msgTypes, }.Build() File_storage_storage_proto = out.File diff --git a/pkg/machinery/config/provider.go b/pkg/machinery/config/provider.go index 8c41e2599b..a62c7f601e 100644 --- a/pkg/machinery/config/provider.go +++ b/pkg/machinery/config/provider.go @@ -25,7 +25,7 @@ type Provider interface { Persist() bool Machine() MachineConfig Cluster() ClusterConfig - Validate(RuntimeMode) error + Validate(RuntimeMode, ...ValidationOption) error ApplyDynamicConfig(context.Context, DynamicConfigProvider) error String() (string, error) Bytes() ([]byte, error) @@ -76,7 +76,7 @@ type File interface { // related options. type Install interface { Image() string - Disk() string + Disk() (string, error) ExtraKernelArgs() []string Zero() bool WithBootloader() bool diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go index 66101f1e80..b08e69b774 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_provider.go @@ -19,6 +19,7 @@ import ( specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/talos-systems/crypto/x509" + "github.com/talos-systems/go-blockdevice/blockdevice/util/disk" talosnet "github.com/talos-systems/net" "github.com/talos-systems/talos/pkg/machinery/config" @@ -1204,8 +1205,62 @@ func (i *InstallConfig) Image() string { } // Disk implements the config.Provider interface. -func (i *InstallConfig) Disk() string { - return i.InstallDisk +func (i *InstallConfig) Disk() (string, error) { + matchers := i.DiskMatchers() + if len(matchers) > 0 { + d, err := disk.Find(matchers...) + if err != nil { + return "", err + } + + if d != nil { + return d.DeviceName, nil + } + + return "", fmt.Errorf("no disk found matching provided parameters") + } + + return i.InstallDisk, nil +} + +// DiskMatchers implements the config.Provider interface. +func (i *InstallConfig) DiskMatchers() []disk.Matcher { + if i.InstallDiskSelector != nil { + selector := i.InstallDiskSelector + + matchers := []disk.Matcher{} + if selector.Size != nil { + matchers = append(matchers, selector.Size.matcher) + } + + if selector.UUID != "" { + matchers = append(matchers, disk.WithUUID(selector.UUID)) + } + + if selector.WWID != "" { + matchers = append(matchers, disk.WithWWID(selector.WWID)) + } + + if selector.Model != "" { + matchers = append(matchers, disk.WithModel(selector.Model)) + } + + if selector.Name != "" { + matchers = append(matchers, disk.WithName(selector.Name)) + } + + if selector.Serial != "" { + matchers = append(matchers, disk.WithSerial(selector.Serial)) + } + + if selector.Modalias != "" { + matchers = append(matchers, disk.WithModalias(selector.Modalias)) + } + + return matchers + } + + return nil } // ExtraKernelArgs implements the config.Provider interface. diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_types.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_types.go index 9a1f23ad2b..699da09240 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_types.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_types.go @@ -17,14 +17,17 @@ package v1alpha1 //go:generate docgen ./v1alpha1_types.go ./v1alpha1_types_doc.go Configuration import ( + "fmt" "net/url" "os" + "regexp" "strconv" "time" humanize "github.com/dustin/go-humanize" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/talos-systems/crypto/x509" + "github.com/talos-systems/go-blockdevice/blockdevice/util/disk" yaml "gopkg.in/yaml.v3" "github.com/talos-systems/talos/pkg/machinery/config" @@ -174,6 +177,25 @@ var ( InstallWipe: false, } + machineInstallDiskSelectorExample = &InstallDiskSelector{ + Model: "WDC*", + Size: &InstallDiskSizeMatcher{ + condition: ">= 1TB", + }, + } + + machineInstallDiskSizeMatcherExamples = []*InstallDiskSizeMatcher{ + { + condition: "4GB", + }, + { + condition: "> 1TB", + }, + { + condition: "<= 2TB", + }, + } + machineFilesExample = []*MachineFile{ { FileContent: "...", @@ -761,6 +783,12 @@ type InstallConfig struct { // - value: '"/dev/nvme0"' InstallDisk string `yaml:"disk,omitempty"` // description: | + // Look up disk using disk characteristics like model, size, serial and others. + // Always has priority over `disk`. + // examples: + // - value: machineInstallDiskSelectorExample + InstallDiskSelector *InstallDiskSelector `yaml:"diskSelector,omitempty"` + // description: | // Allows for supplying extra kernel args via the bootloader. // examples: // - value: '[]string{"talos.platform=metal", "reboot=k"}' @@ -791,6 +819,133 @@ type InstallConfig struct { InstallWipe bool `yaml:"wipe"` } +// InstallDiskSizeMatcher disk size condition parser. +type InstallDiskSizeMatcher struct { + // docgen:nodoc + matcher disk.Matcher + // docgen:nodoc + condition string +} + +// MarshalYAML is a custom marshaller for `InstallDiskSizeMatcher`. +func (m *InstallDiskSizeMatcher) MarshalYAML() (interface{}, error) { + return m.condition, nil +} + +// UnmarshalYAML is a custom unmarshaller for `Endpoint`. +func (m *InstallDiskSizeMatcher) UnmarshalYAML(unmarshal func(interface{}) error) error { + if err := unmarshal(&m.condition); err != nil { + return err + } + + re := regexp.MustCompile(`(>=|<=|>|<|==)?\b*(.+)$`) + + parts := re.FindStringSubmatch(m.condition) + if len(parts) == 0 { + return fmt.Errorf("failed to parse the condition: expected [>=|<=|>|<|==][units], got %s", m.condition) + } + + var compare func(*disk.Disk, uint64) bool + + if len(parts) > 1 { + switch parts[0] { + case ">=": + compare = func(d *disk.Disk, size uint64) bool { + return d.Size >= size + } + case "<=": + compare = func(d *disk.Disk, size uint64) bool { + return d.Size <= size + } + case ">": + compare = func(d *disk.Disk, size uint64) bool { + return d.Size > size + } + case "<": + compare = func(d *disk.Disk, size uint64) bool { + return d.Size < size + } + case "==": + compare = func(d *disk.Disk, size uint64) bool { + return d.Size == size + } + default: + return fmt.Errorf("unknown binary operator %s", parts[0]) + } + } + + size, err := humanize.ParseBytes(parts[1]) + if err != nil { + return err + } + + m.matcher = func(d *disk.Disk) bool { + return compare(d, size) + } + + return nil +} + +// InstallDiskType custom type for disk type selector. +type InstallDiskType disk.Type + +// MarshalYAML is a custom marshaller for `InstallDiskSizeMatcher`. +func (it InstallDiskType) MarshalYAML() (interface{}, error) { + return disk.Type(it).String(), nil +} + +// UnmarshalYAML is a custom unmarshaller for `Endpoint`. +func (it *InstallDiskType) UnmarshalYAML(unmarshal func(interface{}) error) error { + var ( + t string + err error + ) + + if err = unmarshal(&t); err != nil { + return err + } + + if dt, err := disk.ParseType(t); err == nil { + *it = InstallDiskType(dt) + } else { + return err + } + + return nil +} + +// InstallDiskSelector represents a disk query parameters for the install disk lookup. +type InstallDiskSelector struct { + // description: Disk size. + // examples: + // - name: Select a disk which size is equal to 4GB. + // value: machineInstallDiskSizeMatcherExamples[0] + // - name: Select a disk which size is greater than 1TB. + // value: machineInstallDiskSizeMatcherExamples[1] + // - name: Select a disk which size is less or equal than 2TB. + // value: machineInstallDiskSizeMatcherExamples[2] + Size *InstallDiskSizeMatcher `yaml:"size,omitempty"` + // description: Disk name `/sys/block//device/name`. + Name string `yaml:"name,omitempty"` + // description: Disk model `/sys/block//device/model`. + Model string `yaml:"model,omitempty"` + // description: Disk serial number `/sys/block//serial`. + Serial string `yaml:"serial,omitempty"` + // description: Disk modalias `/sys/block//device/modalias`. + Modalias string `yaml:"modalias,omitempty"` + // description: Disk UUID `/sys/block//uuid`. + UUID string `yaml:"uuid,omitempty"` + // description: Disk WWID `/sys/block//wwid`. + WWID string `yaml:"wwid,omitempty"` + // description: Disk Type. + // values: + // - ssd + // - hdd + // - nvme + // - sd + Type InstallDiskType `yaml:"type,omitempty"` +} + // TimeConfig represents the options for configuring time on a machine. type TimeConfig struct { // description: | @@ -864,7 +1019,7 @@ func (e *Endpoint) UnmarshalYAML(unmarshal func(interface{}) error) error { return nil } -// MarshalYAML is a custom unmarshaller for `Endpoint`. +// MarshalYAML is a custom marshaller for `Endpoint`. func (e *Endpoint) MarshalYAML() (interface{}, error) { return e.URL.String(), nil } diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_types_doc.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_types_doc.go index bd8bae0c18..ef63433dcf 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_types_doc.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_types_doc.go @@ -17,6 +17,8 @@ var ( KubeletConfigDoc encoder.Doc NetworkConfigDoc encoder.Doc InstallConfigDoc encoder.Doc + InstallDiskSizeMatcherDoc encoder.Doc + InstallDiskSelectorDoc encoder.Doc TimeConfigDoc encoder.Doc RegistriesConfigDoc encoder.Doc PodCheckpointerDoc encoder.Doc @@ -479,7 +481,7 @@ func init() { FieldName: "install", }, } - InstallConfigDoc.Fields = make([]encoder.Doc, 5) + InstallConfigDoc.Fields = make([]encoder.Doc, 6) InstallConfigDoc.Fields[0].Name = "disk" InstallConfigDoc.Fields[0].Type = "string" InstallConfigDoc.Fields[0].Note = "" @@ -489,43 +491,132 @@ func init() { InstallConfigDoc.Fields[0].AddExample("", "/dev/sda") InstallConfigDoc.Fields[0].AddExample("", "/dev/nvme0") - InstallConfigDoc.Fields[1].Name = "extraKernelArgs" - InstallConfigDoc.Fields[1].Type = "[]string" + InstallConfigDoc.Fields[1].Name = "diskSelector" + InstallConfigDoc.Fields[1].Type = "InstallDiskSelector" InstallConfigDoc.Fields[1].Note = "" - InstallConfigDoc.Fields[1].Description = "Allows for supplying extra kernel args via the bootloader." - InstallConfigDoc.Fields[1].Comments[encoder.LineComment] = "Allows for supplying extra kernel args via the bootloader." + InstallConfigDoc.Fields[1].Description = "Look up disk using disk characteristics like model, size, serial and others.\nAlways has priority over `disk`." + InstallConfigDoc.Fields[1].Comments[encoder.LineComment] = "Look up disk using disk characteristics like model, size, serial and others." - InstallConfigDoc.Fields[1].AddExample("", []string{"talos.platform=metal", "reboot=k"}) - InstallConfigDoc.Fields[2].Name = "image" - InstallConfigDoc.Fields[2].Type = "string" + InstallConfigDoc.Fields[1].AddExample("", machineInstallDiskSelectorExample) + InstallConfigDoc.Fields[2].Name = "extraKernelArgs" + InstallConfigDoc.Fields[2].Type = "[]string" InstallConfigDoc.Fields[2].Note = "" - InstallConfigDoc.Fields[2].Description = "Allows for supplying the image used to perform the installation.\nImage reference for each Talos release can be found on\n[GitHub releases page](https://github.com/talos-systems/talos/releases)." - InstallConfigDoc.Fields[2].Comments[encoder.LineComment] = "Allows for supplying the image used to perform the installation." + InstallConfigDoc.Fields[2].Description = "Allows for supplying extra kernel args via the bootloader." + InstallConfigDoc.Fields[2].Comments[encoder.LineComment] = "Allows for supplying extra kernel args via the bootloader." - InstallConfigDoc.Fields[2].AddExample("", "ghcr.io/talos-systems/installer:latest") - InstallConfigDoc.Fields[3].Name = "bootloader" - InstallConfigDoc.Fields[3].Type = "bool" + InstallConfigDoc.Fields[2].AddExample("", []string{"talos.platform=metal", "reboot=k"}) + InstallConfigDoc.Fields[3].Name = "image" + InstallConfigDoc.Fields[3].Type = "string" InstallConfigDoc.Fields[3].Note = "" - InstallConfigDoc.Fields[3].Description = "Indicates if a bootloader should be installed." - InstallConfigDoc.Fields[3].Comments[encoder.LineComment] = "Indicates if a bootloader should be installed." - InstallConfigDoc.Fields[3].Values = []string{ + InstallConfigDoc.Fields[3].Description = "Allows for supplying the image used to perform the installation.\nImage reference for each Talos release can be found on\n[GitHub releases page](https://github.com/talos-systems/talos/releases)." + InstallConfigDoc.Fields[3].Comments[encoder.LineComment] = "Allows for supplying the image used to perform the installation." + + InstallConfigDoc.Fields[3].AddExample("", "ghcr.io/talos-systems/installer:latest") + InstallConfigDoc.Fields[4].Name = "bootloader" + InstallConfigDoc.Fields[4].Type = "bool" + InstallConfigDoc.Fields[4].Note = "" + InstallConfigDoc.Fields[4].Description = "Indicates if a bootloader should be installed." + InstallConfigDoc.Fields[4].Comments[encoder.LineComment] = "Indicates if a bootloader should be installed." + InstallConfigDoc.Fields[4].Values = []string{ "true", "yes", "false", "no", } - InstallConfigDoc.Fields[4].Name = "wipe" - InstallConfigDoc.Fields[4].Type = "bool" - InstallConfigDoc.Fields[4].Note = "" - InstallConfigDoc.Fields[4].Description = "Indicates if the installation disk should be wiped at installation time.\nDefaults to `true`." - InstallConfigDoc.Fields[4].Comments[encoder.LineComment] = "Indicates if the installation disk should be wiped at installation time." - InstallConfigDoc.Fields[4].Values = []string{ + InstallConfigDoc.Fields[5].Name = "wipe" + InstallConfigDoc.Fields[5].Type = "bool" + InstallConfigDoc.Fields[5].Note = "" + InstallConfigDoc.Fields[5].Description = "Indicates if the installation disk should be wiped at installation time.\nDefaults to `true`." + InstallConfigDoc.Fields[5].Comments[encoder.LineComment] = "Indicates if the installation disk should be wiped at installation time." + InstallConfigDoc.Fields[5].Values = []string{ "true", "yes", "false", "no", } + InstallDiskSizeMatcherDoc.Type = "InstallDiskSizeMatcher" + InstallDiskSizeMatcherDoc.Comments[encoder.LineComment] = "InstallDiskSizeMatcher disk size condition parser." + InstallDiskSizeMatcherDoc.Description = "InstallDiskSizeMatcher disk size condition parser." + + InstallDiskSizeMatcherDoc.AddExample("Select a disk which size is equal to 4GB.", machineInstallDiskSizeMatcherExamples[0]) + + InstallDiskSizeMatcherDoc.AddExample("Select a disk which size is greater than 1TB.", machineInstallDiskSizeMatcherExamples[1]) + + InstallDiskSizeMatcherDoc.AddExample("Select a disk which size is less or equal than 2TB.", machineInstallDiskSizeMatcherExamples[2]) + InstallDiskSizeMatcherDoc.AppearsIn = []encoder.Appearance{ + { + TypeName: "InstallDiskSelector", + FieldName: "size", + }, + } + InstallDiskSizeMatcherDoc.Fields = make([]encoder.Doc, 0) + + InstallDiskSelectorDoc.Type = "InstallDiskSelector" + InstallDiskSelectorDoc.Comments[encoder.LineComment] = "InstallDiskSelector represents a disk query parameters for the install disk lookup." + InstallDiskSelectorDoc.Description = "InstallDiskSelector represents a disk query parameters for the install disk lookup." + + InstallDiskSelectorDoc.AddExample("", machineInstallDiskSelectorExample) + InstallDiskSelectorDoc.AppearsIn = []encoder.Appearance{ + { + TypeName: "InstallConfig", + FieldName: "diskSelector", + }, + } + InstallDiskSelectorDoc.Fields = make([]encoder.Doc, 8) + InstallDiskSelectorDoc.Fields[0].Name = "size" + InstallDiskSelectorDoc.Fields[0].Type = "InstallDiskSizeMatcher" + InstallDiskSelectorDoc.Fields[0].Note = "" + InstallDiskSelectorDoc.Fields[0].Description = "Disk size." + InstallDiskSelectorDoc.Fields[0].Comments[encoder.LineComment] = "Disk size." + + InstallDiskSelectorDoc.Fields[0].AddExample("Select a disk which size is equal to 4GB.", machineInstallDiskSizeMatcherExamples[0]) + + InstallDiskSelectorDoc.Fields[0].AddExample("Select a disk which size is greater than 1TB.", machineInstallDiskSizeMatcherExamples[1]) + + InstallDiskSelectorDoc.Fields[0].AddExample("Select a disk which size is less or equal than 2TB.", machineInstallDiskSizeMatcherExamples[2]) + InstallDiskSelectorDoc.Fields[1].Name = "name" + InstallDiskSelectorDoc.Fields[1].Type = "string" + InstallDiskSelectorDoc.Fields[1].Note = "" + InstallDiskSelectorDoc.Fields[1].Description = "Disk name `/sys/block//device/name`." + InstallDiskSelectorDoc.Fields[1].Comments[encoder.LineComment] = "Disk name `/sys/block//device/name`." + InstallDiskSelectorDoc.Fields[2].Name = "model" + InstallDiskSelectorDoc.Fields[2].Type = "string" + InstallDiskSelectorDoc.Fields[2].Note = "" + InstallDiskSelectorDoc.Fields[2].Description = "Disk model `/sys/block//device/model`." + InstallDiskSelectorDoc.Fields[2].Comments[encoder.LineComment] = "Disk model `/sys/block//device/model`." + InstallDiskSelectorDoc.Fields[3].Name = "serial" + InstallDiskSelectorDoc.Fields[3].Type = "string" + InstallDiskSelectorDoc.Fields[3].Note = "" + InstallDiskSelectorDoc.Fields[3].Description = "Disk serial number `/sys/block//serial`." + InstallDiskSelectorDoc.Fields[3].Comments[encoder.LineComment] = "Disk serial number `/sys/block//serial`." + InstallDiskSelectorDoc.Fields[4].Name = "modalias" + InstallDiskSelectorDoc.Fields[4].Type = "string" + InstallDiskSelectorDoc.Fields[4].Note = "" + InstallDiskSelectorDoc.Fields[4].Description = "Disk modalias `/sys/block//device/modalias`." + InstallDiskSelectorDoc.Fields[4].Comments[encoder.LineComment] = "Disk modalias `/sys/block//device/modalias`." + InstallDiskSelectorDoc.Fields[5].Name = "uuid" + InstallDiskSelectorDoc.Fields[5].Type = "string" + InstallDiskSelectorDoc.Fields[5].Note = "" + InstallDiskSelectorDoc.Fields[5].Description = "Disk UUID `/sys/block//uuid`." + InstallDiskSelectorDoc.Fields[5].Comments[encoder.LineComment] = "Disk UUID `/sys/block//uuid`." + InstallDiskSelectorDoc.Fields[6].Name = "wwid" + InstallDiskSelectorDoc.Fields[6].Type = "string" + InstallDiskSelectorDoc.Fields[6].Note = "" + InstallDiskSelectorDoc.Fields[6].Description = "Disk WWID `/sys/block//wwid`." + InstallDiskSelectorDoc.Fields[6].Comments[encoder.LineComment] = "Disk WWID `/sys/block//wwid`." + InstallDiskSelectorDoc.Fields[7].Name = "type" + InstallDiskSelectorDoc.Fields[7].Type = "InstallDiskType" + InstallDiskSelectorDoc.Fields[7].Note = "" + InstallDiskSelectorDoc.Fields[7].Description = "Disk Type." + InstallDiskSelectorDoc.Fields[7].Comments[encoder.LineComment] = "Disk Type." + InstallDiskSelectorDoc.Fields[7].Values = []string{ + "ssd", + "hdd", + "nvme", + "sd", + } + TimeConfigDoc.Type = "TimeConfig" TimeConfigDoc.Comments[encoder.LineComment] = "TimeConfig represents the options for configuring time on a machine." TimeConfigDoc.Description = "TimeConfig represents the options for configuring time on a machine." @@ -1708,6 +1799,14 @@ func (_ InstallConfig) Doc() *encoder.Doc { return &InstallConfigDoc } +func (_ InstallDiskSizeMatcher) Doc() *encoder.Doc { + return &InstallDiskSizeMatcherDoc +} + +func (_ InstallDiskSelector) Doc() *encoder.Doc { + return &InstallDiskSelectorDoc +} + func (_ TimeConfig) Doc() *encoder.Doc { return &TimeConfigDoc } @@ -1864,6 +1963,8 @@ func GetConfigurationDoc() *encoder.FileDoc { &KubeletConfigDoc, &NetworkConfigDoc, &InstallConfigDoc, + &InstallDiskSizeMatcherDoc, + &InstallDiskSelectorDoc, &TimeConfigDoc, &RegistriesConfigDoc, &PodCheckpointerDoc, diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_validation.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_validation.go index 0a01c3e637..f3d1d58294 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_validation.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_validation.go @@ -57,9 +57,11 @@ type NetworkDeviceCheck func(*Device) error // Validate implements the Configurator interface. //nolint:gocyclo,cyclop -func (c *Config) Validate(mode config.RuntimeMode) error { +func (c *Config) Validate(mode config.RuntimeMode, options ...config.ValidationOption) error { var result *multierror.Error + opts := config.NewValidationOptions(options...) + if c.MachineConfig == nil { result = multierror.Append(result, errors.New("machine instructions are required")) } @@ -73,12 +75,24 @@ func (c *Config) Validate(mode config.RuntimeMode) error { result = multierror.Append(result, fmt.Errorf("install instructions are required in %q mode", mode)) } - if c.MachineConfig.MachineInstall.InstallDisk == "" { - result = multierror.Append(result, fmt.Errorf("an install disk is required in %q mode", mode)) - } + if opts.Local { + if c.MachineConfig.MachineInstall.InstallDisk == "" && len(c.MachineConfig.MachineInstall.DiskMatchers()) == 0 { + result = multierror.Append(result, fmt.Errorf("either install disk or diskSelector should be defined")) + } + } else { + disk, err := c.MachineConfig.MachineInstall.Disk() + + if err != nil { + result = multierror.Append(result, err) + } else { + if disk == "" { + result = multierror.Append(result, fmt.Errorf("an install disk is required in %q mode", mode)) + } - if _, err := os.Stat(c.MachineConfig.MachineInstall.InstallDisk); os.IsNotExist(err) { - result = multierror.Append(result, fmt.Errorf("specified install disk does not exist: %q", c.MachineConfig.MachineInstall.InstallDisk)) + if _, err := os.Stat(disk); os.IsNotExist(err) { + result = multierror.Append(result, fmt.Errorf("specified install disk does not exist: %q", c.MachineConfig.MachineInstall.InstallDisk)) + } + } } } diff --git a/pkg/machinery/config/validation.go b/pkg/machinery/config/validation.go new file mode 100644 index 0000000000..daef036722 --- /dev/null +++ b/pkg/machinery/config/validation.go @@ -0,0 +1,31 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package config + +// ValidationOptions additional validation parameters for the config Validate method. +type ValidationOptions struct { + // Local should disable part of the validation flow which won't work on the host machine. + Local bool +} + +// ValidationOption represents an additional validation parameter for the config Validate method. +type ValidationOption func(opts *ValidationOptions) + +// NewValidationOptions creates new validation options. +func NewValidationOptions(options ...ValidationOption) *ValidationOptions { + opts := &ValidationOptions{} + for _, f := range options { + f(opts) + } + + return opts +} + +// WithLocal enable local flag. +func WithLocal() ValidationOption { + return func(opts *ValidationOptions) { + opts.Local = true + } +} diff --git a/pkg/machinery/go.mod b/pkg/machinery/go.mod index c51f360206..fdffc89326 100644 --- a/pkg/machinery/go.mod +++ b/pkg/machinery/go.mod @@ -16,12 +16,12 @@ require ( github.com/ghodss/yaml v1.0.0 github.com/golang/protobuf v1.5.1 github.com/hashicorp/go-multierror v1.1.1 - github.com/kr/text v0.2.0 // indirect github.com/onsi/ginkgo v1.15.0 // indirect github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2 github.com/stretchr/objx v0.3.0 // indirect github.com/stretchr/testify v1.7.0 github.com/talos-systems/crypto v0.2.1-0.20210202170911-39584f1b6e54 + github.com/talos-systems/go-blockdevice v0.2.1-0.20210322192639-776b37d31de0 github.com/talos-systems/net v0.2.1-0.20210212213224-05190541b0fa github.com/talos-systems/os-runtime v0.0.0-20210303124137-84c3c875eb2b golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect diff --git a/pkg/machinery/go.sum b/pkg/machinery/go.sum index d18258b157..1a61dbe63c 100644 --- a/pkg/machinery/go.sum +++ b/pkg/machinery/go.sum @@ -2,6 +2,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI= github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= @@ -89,6 +90,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= @@ -102,6 +105,10 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/talos-systems/crypto v0.2.1-0.20210202170911-39584f1b6e54 h1:2IGs3f0qG7f33Fv37XuYzIMxLARl4dcpwahZXLmdT2A= github.com/talos-systems/crypto v0.2.1-0.20210202170911-39584f1b6e54/go.mod h1:OXCK52Q0dzm88YRG4VdTBdidkPUtqrCxCyW7bUs4DAw= +github.com/talos-systems/go-blockdevice v0.2.1-0.20210322192639-776b37d31de0 h1:ZOIwpHUR0i2e4VaCe8nwHZuBRjD25XUv+xImyPWHw50= +github.com/talos-systems/go-blockdevice v0.2.1-0.20210322192639-776b37d31de0/go.mod h1:qnn/zDc09I1DA2BUDDCOSA2D0P8pIDjN8pGiRoRaQig= +github.com/talos-systems/go-cmd v0.0.0-20210216164758-68eb0067e0f0/go.mod h1:kf+rZzTEmlDiYQ6ulslvRONnKLQH8x83TowltGMhO+k= +github.com/talos-systems/go-retry v0.1.1-0.20201113203059-8c63d290a688/go.mod h1:HiXQqyVStZ35uSY/MTLWVvQVmC3lIW2MS5VdDaMtoKM= github.com/talos-systems/go-retry v0.2.0/go.mod h1:HiXQqyVStZ35uSY/MTLWVvQVmC3lIW2MS5VdDaMtoKM= github.com/talos-systems/net v0.2.1-0.20210212213224-05190541b0fa h1:XqOMTt0Q6mjsk8Dea5wUpgcdtf+AzesH11m4AozWSxw= github.com/talos-systems/net v0.2.1-0.20210212213224-05190541b0fa/go.mod h1:VreSAyRmxMtqussAHSKMKkJQa1YwBTSVfkmE4Jydam4= @@ -147,11 +154,13 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201130171929-760e229fe7c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -196,6 +205,7 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= diff --git a/website/content/docs/v0.10/Reference/api.md b/website/content/docs/v0.10/Reference/api.md index d9cd1d6f09..979400a5a5 100644 --- a/website/content/docs/v0.10/Reference/api.md +++ b/website/content/docs/v0.10/Reference/api.md @@ -220,6 +220,8 @@ description: Talos gRPC API reference. - [Disks](#storage.Disks) - [DisksResponse](#storage.DisksResponse) + - [Disk.DiskType](#storage.Disk.DiskType) + - [StorageService](#storage.StorageService) - [time/time.proto](#time/time.proto) @@ -3419,6 +3421,12 @@ Disk represents a disk. | size | [uint64](#uint64) | | Size indicates the disk size in bytes. | | model | [string](#string) | | Model idicates the disk model. | | device_name | [string](#string) | | DeviceName indicates the disk name (e.g. `sda`). | +| name | [string](#string) | | Name as in `/sys/block//device/name`. | +| serial | [string](#string) | | Serial as in `/sys/block//device/serial`. | +| modalias | [string](#string) | | Modalias as in `/sys/block//device/modalias`. | +| uuid | [string](#string) | | Uuid as in `/sys/block//device/uuid`. | +| wwid | [string](#string) | | Wwid as in `/sys/block//device/wwid`. | +| type | [Disk.DiskType](#storage.Disk.DiskType) | | Type is a type of the disk: nvme, ssd, hdd, sd card. | @@ -3457,6 +3465,21 @@ DisksResponse represents the response of the `Disks` RPC. + + + +### Disk.DiskType + + +| Name | Number | Description | +| ---- | ------ | ----------- | +| UNKNOWN | 0 | | +| SSD | 1 | | +| HDD | 2 | | +| NVME | 3 | | +| SD | 4 | | + + diff --git a/website/content/docs/v0.10/Reference/cli.md b/website/content/docs/v0.10/Reference/cli.md index d7fe5f3cf9..516d89f9b7 100644 --- a/website/content/docs/v0.10/Reference/cli.md +++ b/website/content/docs/v0.10/Reference/cli.md @@ -672,6 +672,34 @@ talosctl dashboard [flags] * [talosctl](#talosctl) - A CLI for out-of-band management of Kubernetes nodes created by Talos +## talosctl disks + +Get the list of disks from /sys/block on the machine + +``` +talosctl disks [flags] +``` + +### Options + +``` + -h, --help help for disks + -i, --insecure get disks using the insecure (encrypted with no auth) maintenance service +``` + +### Options inherited from parent commands + +``` + --context string Context to be used in command + -e, --endpoints strings override default endpoints in Talos configuration + -n, --nodes strings target the specified nodes + --talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config") +``` + +### SEE ALSO + +* [talosctl](#talosctl) - A CLI for out-of-band management of Kubernetes nodes created by Talos + ## talosctl dmesg Retrieve kernel logs @@ -2018,6 +2046,7 @@ A CLI for out-of-band management of Kubernetes nodes created by Talos * [talosctl copy](#talosctl-copy) - Copy data out from the node * [talosctl crashdump](#talosctl-crashdump) - Dump debug information about the cluster * [talosctl dashboard](#talosctl-dashboard) - Cluster dashboard with real-time metrics +* [talosctl disks](#talosctl-disks) - Get the list of disks from /sys/block on the machine * [talosctl dmesg](#talosctl-dmesg) - Retrieve kernel logs * [talosctl edit](#talosctl-edit) - Edit a resource from the default editor. * [talosctl etcd](#talosctl-etcd) - Manage etcd diff --git a/website/content/docs/v0.10/Reference/configuration.md b/website/content/docs/v0.10/Reference/configuration.md index 9832f3230a..9dc71195a3 100644 --- a/website/content/docs/v0.10/Reference/configuration.md +++ b/website/content/docs/v0.10/Reference/configuration.md @@ -151,6 +151,11 @@ install: image: ghcr.io/talos-systems/installer:latest # Allows for supplying the image used to perform the installation. bootloader: true # Indicates if a bootloader should be installed. wipe: false # Indicates if the installation disk should be wiped at installation time. + + # # Look up disk using disk characteristics like model, size, serial and others. + # diskSelector: + # size: 4GB # Disk size. + # model: WDC* # Disk model `/sys/block//device/model`. ```
@@ -464,6 +469,11 @@ install: image: ghcr.io/talos-systems/installer:latest # Allows for supplying the image used to perform the installation. bootloader: true # Indicates if a bootloader should be installed. wipe: false # Indicates if the installation disk should be wiped at installation time. + + # # Look up disk using disk characteristics like model, size, serial and others. + # diskSelector: + # size: 4GB # Disk size. + # model: WDC* # Disk model `/sys/block//device/model`. ``` @@ -1600,6 +1610,11 @@ extraKernelArgs: image: ghcr.io/talos-systems/installer:latest # Allows for supplying the image used to perform the installation. bootloader: true # Indicates if a bootloader should be installed. wipe: false # Indicates if the installation disk should be wiped at installation time. + +# # Look up disk using disk characteristics like model, size, serial and others. +# diskSelector: +# size: 4GB # Disk size. +# model: WDC* # Disk model `/sys/block//device/model`. ```
@@ -1627,6 +1642,32 @@ disk: /dev/nvme0 ``` + + +
+ +
+ +diskSelector InstallDiskSelector + +
+
+ +Look up disk using disk characteristics like model, size, serial and others. +Always has priority over `disk`. + + + +Examples: + + +``` yaml +diskSelector: + size: 4GB # Disk size. + model: WDC* # Disk model `/sys/block//device/model`. +``` + +

@@ -1734,6 +1775,180 @@ Valid values: +## InstallDiskSizeMatcher +InstallDiskSizeMatcher disk size condition parser. + +Appears in: + + +- InstallDiskSelector.size + + +``` yaml +4GB +``` +``` yaml +'> 1TB' +``` +``` yaml +<= 2TB +``` + + + +## InstallDiskSelector +InstallDiskSelector represents a disk query parameters for the install disk lookup. + +Appears in: + + +- InstallConfig.diskSelector + + +``` yaml +size: 4GB # Disk size. +model: WDC* # Disk model `/sys/block//device/model`. +``` + +
+ + +
+ +Disk size. + + + +Examples: + + +``` yaml +size: 4GB +``` + +``` yaml +size: '> 1TB' +``` + +``` yaml +size: <= 2TB +``` + + +
+ +
+ +
+ +name string + +
+
+ +Disk name `/sys/block//device/name`. + +
+ +
+ +
+ +model string + +
+
+ +Disk model `/sys/block//device/model`. + +
+ +
+ +
+ +serial string + +
+
+ +Disk serial number `/sys/block//serial`. + +
+ +
+ +
+ +modalias string + +
+
+ +Disk modalias `/sys/block//device/modalias`. + +
+ +
+ +
+ +uuid string + +
+
+ +Disk UUID `/sys/block//uuid`. + +
+ +
+ +
+ +wwid string + +
+
+ +Disk WWID `/sys/block//wwid`. + +
+ +
+ +
+ +type InstallDiskType + +
+
+ +Disk Type. + + +Valid values: + + + - ssd + + - hdd + + - nvme + + - sd +
+ +
+ + + + + ## TimeConfig TimeConfig represents the options for configuring time on a machine.