diff --git a/api/resource/definitions/block/block.proto b/api/resource/definitions/block/block.proto index 7e1ad2979..b280bd030 100755 --- a/api/resource/definitions/block/block.proto +++ b/api/resource/definitions/block/block.proto @@ -121,6 +121,7 @@ message MountRequestSpec { repeated string requesters = 3; repeated string requester_i_ds = 4; bool read_only = 5; + bool detached = 6; } // MountSpec is the spec for volume mount. @@ -144,6 +145,7 @@ message MountStatusSpec { bool read_only = 5; bool project_quota_support = 6; talos.resource.definitions.enums.BlockEncryptionProviderType encryption_provider = 7; + bool detached = 8; } // PartitionSpec is the spec for volume partitioning. @@ -219,6 +221,7 @@ message VolumeMountRequestSpec { string volume_id = 1; string requester = 2; bool read_only = 3; + bool detached = 4; } // VolumeMountStatusSpec is the spec for VolumeMountStatus. @@ -227,6 +230,7 @@ message VolumeMountStatusSpec { string requester = 2; string target = 3; bool read_only = 4; + bool detached = 5; } // VolumeStatusSpec is the spec for VolumeStatus resource. diff --git a/cmd/talosctl/cmd/mgmt/cluster/create/create_dev.go b/cmd/talosctl/cmd/mgmt/cluster/create/create_dev.go index 6c0aa3077..10502eedb 100644 --- a/cmd/talosctl/cmd/mgmt/cluster/create/create_dev.go +++ b/cmd/talosctl/cmd/mgmt/cluster/create/create_dev.go @@ -10,6 +10,7 @@ import ( "encoding/base64" "errors" "fmt" + "io/fs" "net" "net/netip" "net/url" @@ -781,7 +782,7 @@ func mergeKubeconfig(ctx context.Context, clusterAccess *access.Adapter) error { _, err = os.Stat(kubeconfigPath) if err != nil { - if !os.IsNotExist(err) { + if !errors.Is(err, fs.ErrNotExist) { return err } diff --git a/cmd/talosctl/cmd/talos/copy.go b/cmd/talosctl/cmd/talos/copy.go index 25dc53190..57bcd9c65 100644 --- a/cmd/talosctl/cmd/talos/copy.go +++ b/cmd/talosctl/cmd/talos/copy.go @@ -6,8 +6,10 @@ package talos import ( "context" + "errors" "fmt" "io" + "io/fs" "os" "path/filepath" @@ -67,7 +69,7 @@ captures ownership and permission bits.`, return fmt.Errorf("local path %q should be a directory", args[1]) } if err != nil { - if !os.IsNotExist(err) { + if !errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("failed to stat local path: %w", err) } if err = os.MkdirAll(localPath, 0o777); err != nil { diff --git a/cmd/talosctl/cmd/talos/edit.go b/cmd/talosctl/cmd/talos/edit.go index de6a75859..b2b5eaefc 100644 --- a/cmd/talosctl/cmd/talos/edit.go +++ b/cmd/talosctl/cmd/talos/edit.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "io/fs" "os" "runtime" "strings" @@ -114,7 +115,7 @@ func editFn(c *client.Client) func(context.Context, string, resource.Resource, e // If we're retrying the loop because of an error, and no change was made in the file, short-circuit if lastError != "" && bytes.Equal(yamlstrip.Comments(editedDiff), yamlstrip.Comments(edited)) { - if _, err = os.Stat(path); !os.IsNotExist(err) { + if _, err = os.Stat(path); !errors.Is(err, fs.ErrNotExist) { message := addEditingComment(lastError) message += fmt.Sprintf("A copy of your changes has been stored to %q\nEdit canceled, no valid changes were saved.\n", path) diff --git a/cmd/talosctl/cmd/talos/kubeconfig.go b/cmd/talosctl/cmd/talos/kubeconfig.go index 55fc9db15..61e488feb 100644 --- a/cmd/talosctl/cmd/talos/kubeconfig.go +++ b/cmd/talosctl/cmd/talos/kubeconfig.go @@ -7,7 +7,9 @@ package talos import ( "bufio" "context" + "errors" "fmt" + "io/fs" "os" "path/filepath" "strings" @@ -70,7 +72,7 @@ If merge flag is false and [local-path] is "-", config will be written to stdout st, err := os.Stat(localPath) if err != nil { - if !os.IsNotExist(err) { + if !errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("error checking path %q: %w", localPath, err) } @@ -87,7 +89,7 @@ If merge flag is false and [local-path] is "-", config will be written to stdout if err == nil && !(kubeconfigFlags.force || kubeconfigFlags.merge) { return fmt.Errorf("kubeconfig file already exists, use --force to overwrite: %q", localPath) } else if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { // merge doesn't make sense if target path doesn't exist kubeconfigFlags.merge = false } else { diff --git a/internal/app/init/main.go b/internal/app/init/main.go index e2e88b2f4..e686eac3b 100644 --- a/internal/app/init/main.go +++ b/internal/app/init/main.go @@ -8,6 +8,7 @@ package main import ( "errors" "fmt" + "io/fs" "log" "os" "os/signal" @@ -124,7 +125,7 @@ func mountRootFS() error { var extensionsConfig extensions.Config if err := extensionsConfig.Read(constants.ExtensionsConfigFile); err != nil { - if !os.IsNotExist(err) { + if !errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("failed to read extensions config: %w", err) } } @@ -210,7 +211,7 @@ func mountRootFS() error { func bindMountFirmware() error { firmwarePath := quirks.New("").FirmwarePath() if _, err := os.Stat(firmwarePath); err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return nil } @@ -224,7 +225,7 @@ func bindMountFirmware() error { func bindMountExtra() error { if _, err := os.Stat(constants.SDStubDynamicInitrdPath); err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return nil } diff --git a/internal/app/machined/internal/server/v1alpha1/v1alpha1_meta.go b/internal/app/machined/internal/server/v1alpha1/v1alpha1_meta.go index 5bedcfcdb..b8b7ab860 100644 --- a/internal/app/machined/internal/server/v1alpha1/v1alpha1_meta.go +++ b/internal/app/machined/internal/server/v1alpha1/v1alpha1_meta.go @@ -6,7 +6,8 @@ package runtime import ( "context" - "os" + "errors" + "io/fs" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -36,7 +37,7 @@ func (s *Server) MetaWrite(ctx context.Context, req *machine.MetaWriteRequest) ( } err = s.Controller.Runtime().State().Machine().Meta().Flush() - if err != nil && !os.IsNotExist(err) { + if err != nil && !errors.Is(err, fs.ErrNotExist) { // ignore not exist error, as it's possible that the meta partition is not created yet return nil, err } @@ -69,7 +70,7 @@ func (s *Server) MetaDelete(ctx context.Context, req *machine.MetaDeleteRequest) } err = s.Controller.Runtime().State().Machine().Meta().Flush() - if err != nil && !os.IsNotExist(err) { + if err != nil && !errors.Is(err, fs.ErrNotExist) { // ignore not exist error, as it's possible that the meta partition is not created yet return nil, err } diff --git a/internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go b/internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go index 7e608c7ac..770e0b54f 100644 --- a/internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go +++ b/internal/app/machined/internal/server/v1alpha1/v1alpha1_server.go @@ -14,6 +14,7 @@ import ( "errors" "fmt" "io" + "io/fs" "log" "net" "os" @@ -1350,7 +1351,7 @@ func getContainerInspector(ctx context.Context, namespace string, driver common. func (s *Server) Read(in *machine.ReadRequest, srv machine.MachineService_ReadServer) (err error) { stat, err := os.Stat(in.Path) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return status.Error(codes.NotFound, err.Error()) } @@ -1938,7 +1939,7 @@ func (s *Server) EtcdSnapshot(in *machine.EtcdSnapshotRequest, srv machine.Machi //nolint:gocyclo func (s *Server) EtcdRecover(srv machine.MachineService_EtcdRecoverServer) error { if _, err := os.Stat(filepath.Dir(constants.EtcdRecoverySnapshotPath)); err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return status.Error(codes.FailedPrecondition, "etcd service is not ready for recovery yet") } diff --git a/internal/app/machined/main.go b/internal/app/machined/main.go index 82cc30adc..5d42732e1 100644 --- a/internal/app/machined/main.go +++ b/internal/app/machined/main.go @@ -228,12 +228,7 @@ func runEntrypoint(ctx context.Context, c *v1alpha1runtime.Controller) error { } }() - controllerWaitGroup.Add(1) - - // Start v2 controller runtime. - go func() { - defer controllerWaitGroup.Done() - + controllerWaitGroup.Go(func() { if e := c.V1Alpha2().Run(ctx, drainer); e != nil { ctrlErr := fmt.Errorf("fatal controller runtime error: %s", e) @@ -243,7 +238,7 @@ func runEntrypoint(ctx context.Context, c *v1alpha1runtime.Controller) error { } log.Printf("controller runtime finished") - }() + }) // Inject controller into maintenance service. maintenance.InjectController(c) diff --git a/internal/app/machined/pkg/adapters/block/mount.go b/internal/app/machined/pkg/adapters/block/mount.go new file mode 100644 index 000000000..9e48814da --- /dev/null +++ b/internal/app/machined/pkg/adapters/block/mount.go @@ -0,0 +1,6 @@ +// 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 block implements adapters wrapping resources/block to provide additional functionality. +package block diff --git a/internal/app/machined/pkg/adapters/block/volume_mount_status.go b/internal/app/machined/pkg/adapters/block/volume_mount_status.go new file mode 100644 index 000000000..72e614ac8 --- /dev/null +++ b/internal/app/machined/pkg/adapters/block/volume_mount_status.go @@ -0,0 +1,52 @@ +// 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 block + +import ( + "fmt" + + "go.uber.org/zap" + + "github.com/siderolabs/talos/pkg/machinery/resources/block" + "github.com/siderolabs/talos/pkg/xfs" + "github.com/siderolabs/talos/pkg/xfs/opentree" +) + +// VolumeMountStatus adapter provides conversion from MountStatus. +// +//nolint:revive,golint +func VolumeMountStatus(r *block.VolumeMountStatus) volumeMountStatus { + return volumeMountStatus{ + VolumeMountStatus: r, + } +} + +type volumeMountStatus struct { + VolumeMountStatus *block.VolumeMountStatus +} + +// WithRoot adapts VolumeMountStatus to xfs.Root and calls the provided callback with it. +func (a volumeMountStatus) WithRoot(logger *zap.Logger, callback func(root xfs.Root) error) error { + var root xfs.Root + + root, ok := a.VolumeMountStatus.TypedSpec().Root().(xfs.Root) + + if !ok || root == nil || !a.VolumeMountStatus.TypedSpec().Detached { + root = &xfs.UnixRoot{ + FS: opentree.NewFromPath(a.VolumeMountStatus.TypedSpec().Target), + } + if err := root.OpenFS(); err != nil { + return fmt.Errorf("error opening filesystem: %w", err) + } + + defer func() { + if err := root.Close(); err != nil { + logger.Error("error closing filesystem", zap.Error(err)) + } + }() + } + + return callback(root) +} diff --git a/internal/app/machined/pkg/adapters/network/nftables_rule.go b/internal/app/machined/pkg/adapters/network/nftables_rule.go index d8a2bd0f0..df5f2b71d 100644 --- a/internal/app/machined/pkg/adapters/network/nftables_rule.go +++ b/internal/app/machined/pkg/adapters/network/nftables_rule.go @@ -6,7 +6,9 @@ package network import ( "cmp" + "errors" "fmt" + "io/fs" "net/netip" "os" "slices" @@ -469,7 +471,7 @@ func (a nftablesRule) Compile() (*NfTablesCompiled, error) { match := a.NfTablesRule.MatchSourceAddress if err := addressMatchExpression(match, "source", 12, 8); err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return &NfTablesCompiled{}, nil } @@ -481,7 +483,7 @@ func (a nftablesRule) Compile() (*NfTablesCompiled, error) { match := a.NfTablesRule.MatchDestinationAddress if err := addressMatchExpression(match, "destination", 16, 24); err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return &NfTablesCompiled{}, nil } diff --git a/internal/app/machined/pkg/automaton/blockautomaton/volume_mount.go b/internal/app/machined/pkg/automaton/blockautomaton/volume_mount.go index 1ca999a54..0b282c1c2 100644 --- a/internal/app/machined/pkg/automaton/blockautomaton/volume_mount.go +++ b/internal/app/machined/pkg/automaton/blockautomaton/volume_mount.go @@ -36,6 +36,7 @@ type VolumeMounterAutomaton = *automaton.ControllerAutomaton[volumeMountContext] // VolumeMounterOptions is the options for the volume mounter controller state machine. type VolumeMounterOptions struct { ReadOnly bool + Detached bool } // VolumeMounterOption is a function that configures the volume mounter controller state machine. @@ -48,6 +49,13 @@ func WithReadOnly(readOnly bool) VolumeMounterOption { } } +// WithDetached sets the volume mounter controller state machine to detached mode. +func WithDetached(detached bool) VolumeMounterOption { + return func(options *VolumeMounterOptions) { + options.Detached = detached + } +} + // NewVolumeMounter creates a new volume mounter controller state machine. // // It ensures that the volume is mounted, and calls the callback function when the volume is mounted, @@ -77,6 +85,7 @@ func createVolumeMountRequest(ctx context.Context, r controller.ReaderWriter, lo req.TypedSpec().VolumeID = mountContext.volumeID req.TypedSpec().Requester = mountContext.requester req.TypedSpec().ReadOnly = mountContext.options.ReadOnly + req.TypedSpec().Detached = mountContext.options.Detached return nil }); err != nil { diff --git a/internal/app/machined/pkg/controllers/block/internal/sysblock/sysblock.go b/internal/app/machined/pkg/controllers/block/internal/sysblock/sysblock.go index de3b872f6..3c7d6b154 100644 --- a/internal/app/machined/pkg/controllers/block/internal/sysblock/sysblock.go +++ b/internal/app/machined/pkg/controllers/block/internal/sysblock/sysblock.go @@ -7,7 +7,9 @@ package sysblock import ( "bytes" + "errors" "fmt" + "io/fs" "os" "path/filepath" @@ -29,7 +31,7 @@ func Walk(root string) ([]*kobject.Event, error) { for _, entry := range entries { fi, err := entry.Info() if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { continue } @@ -42,7 +44,7 @@ func Walk(root string) ([]*kobject.Event, error) { path, err := filepath.EvalSymlinks(filepath.Join(root, entry.Name())) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { continue } @@ -51,7 +53,7 @@ func Walk(root string) ([]*kobject.Event, error) { uevent, err := readUevent(path) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { continue } @@ -103,7 +105,7 @@ func readUevent(path string) (map[string]string, error) { func readPartitions(path string) ([]*kobject.Event, error) { entries, err := os.ReadDir(path) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return nil, nil } @@ -126,7 +128,7 @@ func readPartitions(path string) ([]*kobject.Event, error) { uevent, err := readUevent(partitionPath) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { continue } diff --git a/internal/app/machined/pkg/controllers/block/internal/volumes/format.go b/internal/app/machined/pkg/controllers/block/internal/volumes/format.go index 0f542900a..be8f17138 100644 --- a/internal/app/machined/pkg/controllers/block/internal/volumes/format.go +++ b/internal/app/machined/pkg/controllers/block/internal/volumes/format.go @@ -18,10 +18,10 @@ import ( "go.uber.org/zap" mountv3 "github.com/siderolabs/talos/internal/pkg/mount/v3" - "github.com/siderolabs/talos/internal/pkg/xfs/fsopen" "github.com/siderolabs/talos/pkg/machinery/imager/quirks" "github.com/siderolabs/talos/pkg/machinery/resources/block" "github.com/siderolabs/talos/pkg/makefs" + "github.com/siderolabs/talos/pkg/xfs/fsopen" ) // Format establishes a filesystem on a block device. @@ -164,7 +164,6 @@ func GrowFilesystem(logger *zap.Logger, volumeContext ManagerContext) error { mountv3.WithFsopen( volumeContext.Cfg.TypedSpec().Provisioning.FilesystemSpec.Type.String(), fsopen.WithSource(volumeContext.Status.MountLocation), - fsopen.WithPrinter(logger.Sugar().Infof), ), ) diff --git a/internal/app/machined/pkg/controllers/block/mount.go b/internal/app/machined/pkg/controllers/block/mount.go index ce4492781..38d5dcf29 100644 --- a/internal/app/machined/pkg/controllers/block/mount.go +++ b/internal/app/machined/pkg/controllers/block/mount.go @@ -6,7 +6,9 @@ package block import ( "context" + "errors" "fmt" + "io/fs" "os" "path/filepath" "slices" @@ -19,14 +21,14 @@ import ( "github.com/siderolabs/gen/xslices" "github.com/siderolabs/go-blockdevice/v2/swap" "go.uber.org/zap" - "golang.org/x/sys/unix" "github.com/siderolabs/talos/internal/pkg/mount/v3" "github.com/siderolabs/talos/internal/pkg/selinux" - "github.com/siderolabs/talos/internal/pkg/xfs/fsopen" "github.com/siderolabs/talos/pkg/filetree" "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/machinery/resources/block" + "github.com/siderolabs/talos/pkg/xfs" + "github.com/siderolabs/talos/pkg/xfs/fsopen" ) type mountContext struct { @@ -232,6 +234,15 @@ func (ctrl *MountController) Run(ctx context.Context, r controller.Runtime, logg mountStatus.TypedSpec().EncryptionProvider = volumeStatus.TypedSpec().EncryptionProvider mountStatus.TypedSpec().ReadOnly = mountRequest.TypedSpec().ReadOnly mountStatus.TypedSpec().ProjectQuotaSupport = volumeStatus.TypedSpec().MountSpec.ProjectQuotaSupport + mountStatus.TypedSpec().Detached = mountRequest.TypedSpec().Detached + + // This needs to be set through accessor, and is not guaranteed to resolve to a valid root. + mount, ok := ctrl.activeMounts[mountRequest.Metadata().ID()] + if ok && mount.point != nil { + mountStatus.TypedSpec().SetRoot(mount.point.Root()) + } else { + mountStatus.TypedSpec().SetRoot(&xfs.OSRoot{Shadow: filepath.Join(rootPath, mountTarget)}) + } return nil }, @@ -341,7 +352,7 @@ func (ctrl *MountController) handleSymlinkMountOperation( targetPath := filepath.Join(rootPath, target) st, err := os.Lstat(targetPath) - if err != nil && !os.IsNotExist(err) { + if err != nil && !errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("failed to stat target path: %w", err) } @@ -450,7 +461,7 @@ func (ctrl *MountController) updateTargetSettings( } if err != nil { - return fmt.Errorf("error setting label %q: %w", targetPath, err) + return fmt.Errorf("error setting label on %q: %w", currentLabel, err) } return nil @@ -475,7 +486,6 @@ func (ctrl *MountController) handleDiskMountOperation( fsOpts = append(fsOpts, fsopen.WithSource(mountSource), - fsopen.WithPrinter(logger.Sugar().With(zap.String("volume", volumeStatus.Metadata().ID())).Infof), fsopen.WithProjectQuota(volumeStatus.TypedSpec().MountSpec.ProjectQuotaSupport), ) @@ -484,7 +494,11 @@ func (ctrl *MountController) handleDiskMountOperation( ) if mountRequest.TypedSpec().ReadOnly { - opts = append(opts, mount.WithMountAttributes(unix.MOUNT_ATTR_RDONLY)) + opts = append(opts, mount.WithReadOnly()) + } + + if mountRequest.TypedSpec().Detached { + opts = append(opts, mount.WithDetached()) } manager := mount.NewManager(slices.Concat( @@ -504,7 +518,7 @@ func (ctrl *MountController) handleDiskMountOperation( return fmt.Errorf("failed to mount %q: %w", mountRequest.Metadata().ID(), err) } - if !mountRequest.TypedSpec().ReadOnly { + if !mountRequest.TypedSpec().ReadOnly && !mountRequest.TypedSpec().Detached { if err = ctrl.updateTargetSettings(mountTarget, volumeStatus.TypedSpec().MountSpec); err != nil { manager.Unmount() //nolint:errcheck @@ -518,6 +532,7 @@ func (ctrl *MountController) handleDiskMountOperation( zap.String("target", mountTarget), zap.Stringer("filesystem", mountFilesystem), zap.Bool("read_only", mountRequest.TypedSpec().ReadOnly), + zap.Bool("detached", mountRequest.TypedSpec().Detached), ) ctrl.activeMounts[mountRequest.Metadata().ID()] = &mountContext{ diff --git a/internal/app/machined/pkg/controllers/block/mount_request.go b/internal/app/machined/pkg/controllers/block/mount_request.go index d227be877..a11c6b7b2 100644 --- a/internal/app/machined/pkg/controllers/block/mount_request.go +++ b/internal/app/machined/pkg/controllers/block/mount_request.go @@ -105,6 +105,7 @@ func (ctrl *MountRequestController) Run(ctx context.Context, r controller.Runtim desiredMountRequests[volumeID] = &block.MountRequestSpec{ VolumeID: volumeID, ReadOnly: volumeMountRequest.TypedSpec().ReadOnly, + Detached: volumeMountRequest.TypedSpec().Detached, } } @@ -112,6 +113,7 @@ func (ctrl *MountRequestController) Run(ctx context.Context, r controller.Runtim desiredMountRequest.Requesters = append(desiredMountRequest.Requesters, volumeMountRequest.TypedSpec().Requester) desiredMountRequest.RequesterIDs = append(desiredMountRequest.RequesterIDs, volumeMountRequest.Metadata().ID()) desiredMountRequest.ReadOnly = desiredMountRequest.ReadOnly && volumeMountRequest.TypedSpec().ReadOnly // read-only if all requesters are read-only + desiredMountRequest.Detached = desiredMountRequest.Detached && volumeMountRequest.TypedSpec().Detached // detached if all requesters are detached desiredMountRequest.ParentMountID = volumeStatus.TypedSpec().MountSpec.ParentID } diff --git a/internal/app/machined/pkg/controllers/block/mount_status.go b/internal/app/machined/pkg/controllers/block/mount_status.go index c4e3e912e..77acfdf42 100644 --- a/internal/app/machined/pkg/controllers/block/mount_status.go +++ b/internal/app/machined/pkg/controllers/block/mount_status.go @@ -90,6 +90,10 @@ func (ctrl *MountStatusController) Run(ctx context.Context, r controller.Runtime vms.TypedSpec().Target = mountStatus.TypedSpec().Target vms.TypedSpec().VolumeID = mountStatus.TypedSpec().Spec.VolumeID vms.TypedSpec().ReadOnly = mountStatus.TypedSpec().Spec.ReadOnly + vms.TypedSpec().Detached = mountStatus.TypedSpec().Detached + + // This needs to be set through accessor, and is not guaranteed to resolve to a valid root. + vms.TypedSpec().SetRoot(mountStatus.TypedSpec().Root()) return nil }, diff --git a/internal/app/machined/pkg/controllers/cluster/node_identity.go b/internal/app/machined/pkg/controllers/cluster/node_identity.go index d9e0eadee..521223726 100644 --- a/internal/app/machined/pkg/controllers/cluster/node_identity.go +++ b/internal/app/machined/pkg/controllers/cluster/node_identity.go @@ -12,15 +12,15 @@ import ( "github.com/cosi-project/runtime/pkg/safe" "go.uber.org/zap" + blockadapter "github.com/siderolabs/talos/internal/app/machined/pkg/adapters/block" clusteradapter "github.com/siderolabs/talos/internal/app/machined/pkg/adapters/cluster" "github.com/siderolabs/talos/internal/app/machined/pkg/automaton/blockautomaton" "github.com/siderolabs/talos/internal/app/machined/pkg/controllers" - "github.com/siderolabs/talos/internal/pkg/xfs" - "github.com/siderolabs/talos/internal/pkg/xfs/opentree" "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/machinery/resources/block" "github.com/siderolabs/talos/pkg/machinery/resources/cluster" "github.com/siderolabs/talos/pkg/machinery/resources/files" + "github.com/siderolabs/talos/pkg/xfs" ) // NodeIdentityController manages runtime.Identity caching identity in the STATE. @@ -79,7 +79,12 @@ func (ctrl *NodeIdentityController) Run(ctx context.Context, r controller.Runtim } if ctrl.stateMachine == nil { - ctrl.stateMachine = blockautomaton.NewVolumeMounter(ctrl.Name(), constants.StatePartitionLabel, ctrl.establishNodeIdentity) + ctrl.stateMachine = blockautomaton.NewVolumeMounter( + ctrl.Name(), + constants.StatePartitionLabel, + ctrl.establishNodeIdentity, + blockautomaton.WithDetached(true), + ) } if err := ctrl.stateMachine.Run(ctx, r, logger); err != nil { @@ -91,48 +96,39 @@ func (ctrl *NodeIdentityController) Run(ctx context.Context, r controller.Runtim } func (ctrl *NodeIdentityController) establishNodeIdentity(ctx context.Context, r controller.ReaderWriter, logger *zap.Logger, mountStatus *block.VolumeMountStatus) error { - root := &xfs.UnixRoot{FS: opentree.NewFromPath(mountStatus.TypedSpec().Target)} - if err := root.OpenFS(); err != nil { - return fmt.Errorf("error opening filesystem: %w", err) - } + return blockadapter.VolumeMountStatus(mountStatus).WithRoot(logger, func(root xfs.Root) error { + var localIdentity cluster.IdentitySpec - defer func() { - if err := root.Close(); err != nil { - logger.Error("error closing filesystem", zap.Error(err)) + if err := controllers.LoadOrNewFromFile(root, constants.NodeIdentityFilename, &localIdentity, func(v *cluster.IdentitySpec) error { + return clusteradapter.IdentitySpec(v).Generate() + }); err != nil { + return fmt.Errorf("error caching node identity: %w", err) } - }() - var localIdentity cluster.IdentitySpec + if err := safe.WriterModify(ctx, r, cluster.NewIdentity(cluster.NamespaceName, cluster.LocalIdentity), func(r *cluster.Identity) error { + *r.TypedSpec() = localIdentity - if err := controllers.LoadOrNewFromFile(root, constants.NodeIdentityFilename, &localIdentity, func(v *cluster.IdentitySpec) error { - return clusteradapter.IdentitySpec(v).Generate() - }); err != nil { - return fmt.Errorf("error caching node identity: %w", err) - } + return nil + }); err != nil { + return fmt.Errorf("error modifying resource: %w", err) + } - if err := safe.WriterModify(ctx, r, cluster.NewIdentity(cluster.NamespaceName, cluster.LocalIdentity), func(r *cluster.Identity) error { - *r.TypedSpec() = localIdentity + // generate `/etc/machine-id` from node identity + if err := safe.WriterModify(ctx, r, files.NewEtcFileSpec(files.NamespaceName, "machine-id"), + func(r *files.EtcFileSpec) error { + var err error + + r.TypedSpec().Contents, err = clusteradapter.IdentitySpec(&localIdentity).ConvertMachineID() + r.TypedSpec().Mode = 0o444 + r.TypedSpec().SelinuxLabel = constants.EtcSelinuxLabel + + return err + }); err != nil { + return fmt.Errorf("error modifying machine-id: %w", err) + } + + logger.Info("node identity established", zap.String("node_id", localIdentity.NodeID)) return nil - }); err != nil { - return fmt.Errorf("error modifying resource: %w", err) - } - - // generate `/etc/machine-id` from node identity - if err := safe.WriterModify(ctx, r, files.NewEtcFileSpec(files.NamespaceName, "machine-id"), - func(r *files.EtcFileSpec) error { - var err error - - r.TypedSpec().Contents, err = clusteradapter.IdentitySpec(&localIdentity).ConvertMachineID() - r.TypedSpec().Mode = 0o444 - r.TypedSpec().SelinuxLabel = constants.EtcSelinuxLabel - - return err - }); err != nil { - return fmt.Errorf("error modifying machine-id: %w", err) - } - - logger.Info("node identity established", zap.String("node_id", localIdentity.NodeID)) - - return nil + }) } diff --git a/internal/app/machined/pkg/controllers/config/acquire.go b/internal/app/machined/pkg/controllers/config/acquire.go index 98a3fc2c6..286923ddb 100644 --- a/internal/app/machined/pkg/controllers/config/acquire.go +++ b/internal/app/machined/pkg/controllers/config/acquire.go @@ -12,9 +12,8 @@ import ( "errors" "fmt" "io" + "io/fs" "net/http" - "os" - "path/filepath" "slices" "strings" @@ -26,6 +25,7 @@ import ( "github.com/siderolabs/go-procfs/procfs" "go.uber.org/zap" + blockadapter "github.com/siderolabs/talos/internal/app/machined/pkg/adapters/block" "github.com/siderolabs/talos/internal/app/machined/pkg/automaton" "github.com/siderolabs/talos/internal/app/machined/pkg/automaton/blockautomaton" talosruntime "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" @@ -40,6 +40,7 @@ import ( configresource "github.com/siderolabs/talos/pkg/machinery/resources/config" "github.com/siderolabs/talos/pkg/machinery/resources/runtime" "github.com/siderolabs/talos/pkg/machinery/resources/v1alpha1" + "github.com/siderolabs/talos/pkg/xfs" ) // PlatformConfigurator is a reduced interface of runtime.Platform. @@ -297,8 +298,12 @@ func (validationModeDiskConfig) String() string { // loadFromDisk is a helper function for stateDisk. func (ctrl *AcquireController) loadFromDisk(ctx context.Context, r controller.ReaderWriter, logger *zap.Logger) (config.Provider, bool, error) { if ctrl.stateMachine == nil { - ctrl.stateMachine = blockautomaton.NewVolumeMounter(ctrl.Name(), constants.StatePartitionLabel, ctrl.loadConfigFromDisk, + ctrl.stateMachine = blockautomaton.NewVolumeMounter( + ctrl.Name(), + constants.StatePartitionLabel, + ctrl.loadConfigFromDisk, blockautomaton.WithReadOnly(true), + blockautomaton.WithDetached(true), ) } @@ -321,39 +326,41 @@ func (ctrl *AcquireController) loadFromDisk(ctx context.Context, r controller.Re } func (ctrl *AcquireController) loadConfigFromDisk(ctx context.Context, r controller.ReaderWriter, logger *zap.Logger, mountStatus *block.VolumeMountStatus) error { - configPath := filepath.Join(mountStatus.TypedSpec().Target, constants.ConfigFilename) + return blockadapter.VolumeMountStatus(mountStatus).WithRoot(logger, func(root xfs.Root) error { + configPath := constants.ConfigFilename - logger.Debug("loading config from STATE", zap.String("path", configPath)) + logger.Debug("loading config from STATE", zap.String("path", configPath)) - _, err := os.Stat(configPath) - if err != nil { - if os.IsNotExist(err) { - // no saved machine config - return nil + _, err := xfs.Stat(root, configPath) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + // no saved machine config + return nil + } + + return fmt.Errorf("failed to stat %s: %w", configPath, err) } - return fmt.Errorf("failed to stat %s: %w", configPath, err) - } + cfg, err := loadConfig(root, configPath) + if err != nil { + return err + } - cfg, err := configloader.NewFromFile(configPath) - if err != nil { - return fmt.Errorf("failed to load config from STATE: %w", err) - } + // if the STATE partition is present & contains machine config, Talos is already installed + warnings, err := cfg.Validate(validationModeDiskConfig{}) + if err != nil { + return fmt.Errorf("failed to validate on-disk config: %w", err) + } - // if the STATE partition is present & contains machine config, Talos is already installed - warnings, err := cfg.Validate(validationModeDiskConfig{}) - if err != nil { - return fmt.Errorf("failed to validate on-disk config: %w", err) - } + for _, warning := range warnings { + logger.Warn("config validation warning", zap.String("warning", warning)) + } - for _, warning := range warnings { - logger.Warn("config validation warning", zap.String("warning", warning)) - } + // we can't return the value directly + ctrl.diskConfig = cfg - // we can't return the value directly - ctrl.diskConfig = cfg - - return nil + return nil + }) } // stateCmdlineEarly acquires machine configuration from the kernel cmdline source (talos.config.early). @@ -687,3 +694,18 @@ func (ctrl *AcquireController) stateDone(ctx context.Context, r controller.Runti func (ctrl *AcquireController) stateFinal(ctx context.Context, r controller.Runtime, logger *zap.Logger) (stateMachineFunc, config.Provider, error) { return nil, nil, nil } + +func loadConfig(root xfs.Root, configPath string) (config.Provider, error) { + f, err := xfs.Open(root, configPath) + if err != nil { + return nil, fmt.Errorf("failed to open %q from STATE: %w", configPath, err) + } + defer f.Close() //nolint:errcheck + + cfg, err := configloader.NewFromReader(f) + if err != nil { + return nil, fmt.Errorf("failed to load %q from STATE: %w", configPath, err) + } + + return cfg, nil +} diff --git a/internal/app/machined/pkg/controllers/config/acquire_test.go b/internal/app/machined/pkg/controllers/config/acquire_test.go index 88459c3fc..1561a8052 100644 --- a/internal/app/machined/pkg/controllers/config/acquire_test.go +++ b/internal/app/machined/pkg/controllers/config/acquire_test.go @@ -336,10 +336,10 @@ func (suite *AcquireSuite) TestFromDiskFailure() { ev := suite.platformEvent.getEvents()[0] suite.Assert().Equal(platform.EventTypeFailure, ev.Type) suite.Assert().Equal("Error loading and validating Talos machine config.", ev.Message) - suite.Assert().Equal("failed to load config from STATE: unknown keys found during decoding:\naaaversion: v1alpha1 # Indicates the schema used to decode the contents.\n", ev.Error.Error()) + suite.Assert().Equal("failed to load \"config.yaml\" from STATE: unknown keys found during decoding:\naaaversion: v1alpha1 # Indicates the schema used to decode the contents.\n", ev.Error.Error()) suite.Assert().Equal(&machineapi.ConfigLoadErrorEvent{ - Error: "failed to load config from STATE: unknown keys found during decoding:\naaaversion: v1alpha1 # Indicates the schema used to decode the contents.\n", + Error: "failed to load \"config.yaml\" from STATE: unknown keys found during decoding:\naaaversion: v1alpha1 # Indicates the schema used to decode the contents.\n", }, suite.eventPublisher.getEvents()[0]) } diff --git a/internal/app/machined/pkg/controllers/config/persistence.go b/internal/app/machined/pkg/controllers/config/persistence.go index 2dadbe5ed..59023ba18 100644 --- a/internal/app/machined/pkg/controllers/config/persistence.go +++ b/internal/app/machined/pkg/controllers/config/persistence.go @@ -7,8 +7,6 @@ package config import ( "context" "fmt" - "os" - "path/filepath" "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/resource" @@ -17,11 +15,13 @@ import ( "github.com/siderolabs/gen/optional" "go.uber.org/zap" + blockadapter "github.com/siderolabs/talos/internal/app/machined/pkg/adapters/block" "github.com/siderolabs/talos/internal/app/machined/pkg/automaton" "github.com/siderolabs/talos/internal/app/machined/pkg/automaton/blockautomaton" "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/machinery/resources/block" "github.com/siderolabs/talos/pkg/machinery/resources/config" + "github.com/siderolabs/talos/pkg/xfs" ) // PersistenceController ensures that the machine configuration is persisted in STATE partition. @@ -112,7 +112,12 @@ func (ctrl *PersistenceController) Run(ctx context.Context, r controller.Runtime } if ctrl.stateMachine == nil && ctrl.configToPersist != nil { - ctrl.stateMachine = blockautomaton.NewVolumeMounter(ctrl.Name(), constants.StatePartitionLabel, ctrl.persistMachineConfig) + ctrl.stateMachine = blockautomaton.NewVolumeMounter( + ctrl.Name(), + constants.StatePartitionLabel, + ctrl.persistMachineConfig, + blockautomaton.WithDetached(true), + ) } if ctrl.stateMachine != nil { @@ -143,26 +148,27 @@ func (ctrl *PersistenceController) Run(ctx context.Context, r controller.Runtime } func (ctrl *PersistenceController) persistMachineConfig(ctx context.Context, r controller.ReaderWriter, logger *zap.Logger, mountStatus *block.VolumeMountStatus) error { - rootPath := mountStatus.TypedSpec().Target - tempName := constants.ConfigFilename + "-tmp" + return blockadapter.VolumeMountStatus(mountStatus).WithRoot(logger, func(root xfs.Root) error { + tempName := constants.ConfigFilename + "-tmp" - configContents, err := ctrl.configToPersist.Provider().Bytes() - if err != nil { - return fmt.Errorf("error getting config bytes: %w", err) - } + configContents, err := ctrl.configToPersist.Provider().Bytes() + if err != nil { + return fmt.Errorf("error getting config bytes: %w", err) + } - if err = os.WriteFile(filepath.Join(rootPath, tempName), configContents, 0o600); err != nil { - return fmt.Errorf("error writing config to file: %w", err) - } + if err = xfs.WriteFile(root, tempName, configContents, 0o600); err != nil { + return fmt.Errorf("error writing config to file: %w", err) + } - if err = os.Rename(filepath.Join(rootPath, tempName), filepath.Join(rootPath, constants.ConfigFilename)); err != nil { - return fmt.Errorf("error renaming config file: %w", err) - } + if err = xfs.Rename(root, tempName, constants.ConfigFilename); err != nil { + return fmt.Errorf("error renaming config file: %w", err) + } - logger.Info("machine configuration persisted to STATE") + logger.Info("machine configuration persisted to STATE") - ctrl.lastPersistedVersion = ctrl.configToPersist.Metadata().Version() - ctrl.configToPersist = nil + ctrl.lastPersistedVersion = ctrl.configToPersist.Metadata().Version() + ctrl.configToPersist = nil - return nil + return nil + }) } diff --git a/internal/app/machined/pkg/controllers/files/cri_registry_config.go b/internal/app/machined/pkg/controllers/files/cri_registry_config.go index f2ade21c7..77a666333 100644 --- a/internal/app/machined/pkg/controllers/files/cri_registry_config.go +++ b/internal/app/machined/pkg/controllers/files/cri_registry_config.go @@ -19,10 +19,10 @@ import ( "go.uber.org/zap" "github.com/siderolabs/talos/internal/pkg/containers/cri/containerd" - "github.com/siderolabs/talos/internal/pkg/xfs" "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/machinery/resources/cri" "github.com/siderolabs/talos/pkg/machinery/resources/files" + "github.com/siderolabs/talos/pkg/xfs" ) // CRIRegistryConfigController generates parts of the CRI config for registry configuration. diff --git a/internal/app/machined/pkg/controllers/files/etcfile.go b/internal/app/machined/pkg/controllers/files/etcfile.go index 5e0dfb4ba..e3526888c 100644 --- a/internal/app/machined/pkg/controllers/files/etcfile.go +++ b/internal/app/machined/pkg/controllers/files/etcfile.go @@ -20,8 +20,8 @@ import ( "github.com/siderolabs/talos/internal/pkg/mount/v3" "github.com/siderolabs/talos/internal/pkg/selinux" - "github.com/siderolabs/talos/internal/pkg/xfs" "github.com/siderolabs/talos/pkg/machinery/resources/files" + "github.com/siderolabs/talos/pkg/xfs" ) // EtcFileController watches EtcFileSpecs, creates/updates files. diff --git a/internal/app/machined/pkg/controllers/files/etcfile_test.go b/internal/app/machined/pkg/controllers/files/etcfile_test.go index 88c31e780..25da6ce53 100644 --- a/internal/app/machined/pkg/controllers/files/etcfile_test.go +++ b/internal/app/machined/pkg/controllers/files/etcfile_test.go @@ -25,9 +25,9 @@ import ( filesctrl "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/files" "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" - "github.com/siderolabs/talos/internal/pkg/xfs" - "github.com/siderolabs/talos/internal/pkg/xfs/opentree" "github.com/siderolabs/talos/pkg/machinery/resources/files" + "github.com/siderolabs/talos/pkg/xfs" + "github.com/siderolabs/talos/pkg/xfs/opentree" ) type EtcFileSuite struct { diff --git a/internal/app/machined/pkg/controllers/hardware/pci_driver_rebind.go b/internal/app/machined/pkg/controllers/hardware/pci_driver_rebind.go index e968dd827..e7cec12b3 100644 --- a/internal/app/machined/pkg/controllers/hardware/pci_driver_rebind.go +++ b/internal/app/machined/pkg/controllers/hardware/pci_driver_rebind.go @@ -6,7 +6,9 @@ package hardware import ( "context" + "errors" "fmt" + "io/fs" "os" "path/filepath" @@ -154,7 +156,7 @@ func (c *PCIDriverRebindController) handlePCIDriverReBind(pciID, targetDriver st // Unbind device from the host driver. // in some cases, the device may not be bound to any driver, so we ignore the error. - if err := os.WriteFile(fmt.Sprintf(driverUnbindPath, pciID), []byte(pciID), 0o200); err != nil && !os.IsNotExist(err) { + if err := os.WriteFile(fmt.Sprintf(driverUnbindPath, pciID), []byte(pciID), 0o200); err != nil && !errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("error unbinding device with id: %s, %w", pciID, err) } diff --git a/internal/app/machined/pkg/controllers/hardware/pcidevices.go b/internal/app/machined/pkg/controllers/hardware/pcidevices.go index b847b462c..6feff1a9b 100644 --- a/internal/app/machined/pkg/controllers/hardware/pcidevices.go +++ b/internal/app/machined/pkg/controllers/hardware/pcidevices.go @@ -7,7 +7,9 @@ package hardware import ( "bytes" "context" + "errors" "fmt" + "io/fs" "os" "path/filepath" "strconv" @@ -100,7 +102,7 @@ func (ctrl *PCIDevicesController) Run(ctx context.Context, r controller.Runtime, for _, deviceID := range deviceIDs { class, err := readHexPCIInfo(deviceID.Name(), "class") if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { continue } @@ -109,7 +111,7 @@ func (ctrl *PCIDevicesController) Run(ctx context.Context, r controller.Runtime, vendor, err := readHexPCIInfo(deviceID.Name(), "vendor") if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { continue } @@ -118,7 +120,7 @@ func (ctrl *PCIDevicesController) Run(ctx context.Context, r controller.Runtime, product, err := readHexPCIInfo(deviceID.Name(), "device") if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { continue } @@ -176,7 +178,7 @@ func readDriverInfo(deviceID string) (string, error) { if err != nil { // ignore if the driver doesn't exist // this can happen if the device is not bound to a driver or a pci root port - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return "", nil } diff --git a/internal/app/machined/pkg/controllers/kubespan/identity.go b/internal/app/machined/pkg/controllers/kubespan/identity.go index eff843064..d792df926 100644 --- a/internal/app/machined/pkg/controllers/kubespan/identity.go +++ b/internal/app/machined/pkg/controllers/kubespan/identity.go @@ -15,18 +15,18 @@ import ( "github.com/siderolabs/gen/optional" "go.uber.org/zap" + blockadapter "github.com/siderolabs/talos/internal/app/machined/pkg/adapters/block" kubespanadapter "github.com/siderolabs/talos/internal/app/machined/pkg/adapters/kubespan" "github.com/siderolabs/talos/internal/app/machined/pkg/automaton" "github.com/siderolabs/talos/internal/app/machined/pkg/automaton/blockautomaton" "github.com/siderolabs/talos/internal/app/machined/pkg/controllers" - "github.com/siderolabs/talos/internal/pkg/xfs" - "github.com/siderolabs/talos/internal/pkg/xfs/opentree" "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/machinery/fipsmode" "github.com/siderolabs/talos/pkg/machinery/resources/block" "github.com/siderolabs/talos/pkg/machinery/resources/config" "github.com/siderolabs/talos/pkg/machinery/resources/kubespan" "github.com/siderolabs/talos/pkg/machinery/resources/network" + "github.com/siderolabs/talos/pkg/xfs" ) // IdentityController watches KubeSpan configuration, updates KubeSpan Identity. @@ -111,7 +111,12 @@ func (ctrl *IdentityController) Run(ctx context.Context, r controller.Runtime, l } if ctrl.stateMachine == nil && !alreadyHasIdentity { - ctrl.stateMachine = blockautomaton.NewVolumeMounter(ctrl.Name(), constants.StatePartitionLabel, ctrl.establishIdentity(cfg, firstMAC)) + ctrl.stateMachine = blockautomaton.NewVolumeMounter( + ctrl.Name(), + constants.StatePartitionLabel, + ctrl.establishIdentity(cfg, firstMAC), + blockautomaton.WithDetached(true), + ) } } else if alreadyHasIdentity { if err = r.Destroy(ctx, kubespan.NewIdentity(kubespan.NamespaceName, kubespan.LocalIdentity).Metadata()); err != nil { @@ -141,36 +146,27 @@ func (ctrl *IdentityController) establishIdentity( ctx context.Context, r controller.ReaderWriter, logger *zap.Logger, mountStatus *block.VolumeMountStatus, ) error { return func(ctx context.Context, r controller.ReaderWriter, logger *zap.Logger, mountStatus *block.VolumeMountStatus) error { - root := &xfs.UnixRoot{FS: opentree.NewFromPath(mountStatus.TypedSpec().Target)} - if err := root.OpenFS(); err != nil { - return fmt.Errorf("error opening filesystem: %w", err) - } + return blockadapter.VolumeMountStatus(mountStatus).WithRoot(logger, func(root xfs.Root) error { + var localIdentity kubespan.IdentitySpec - defer func() { - if err := root.Close(); err != nil { - logger.Error("error closing filesystem", zap.Error(err)) + if err := controllers.LoadOrNewFromFile(root, constants.KubeSpanIdentityFilename, &localIdentity, func(v *kubespan.IdentitySpec) error { + return kubespanadapter.IdentitySpec(v).GenerateKey() + }); err != nil { + return fmt.Errorf("error caching kubespan identity: %w", err) } - }() - var localIdentity kubespan.IdentitySpec + kubespanCfg := cfg.TypedSpec() + mac := firstMAC.TypedSpec() - if err := controllers.LoadOrNewFromFile(root, constants.KubeSpanIdentityFilename, &localIdentity, func(v *kubespan.IdentitySpec) error { - return kubespanadapter.IdentitySpec(v).GenerateKey() - }); err != nil { - return fmt.Errorf("error caching kubespan identity: %w", err) - } + if err := kubespanadapter.IdentitySpec(&localIdentity).UpdateAddress(kubespanCfg.ClusterID, net.HardwareAddr(mac.HardwareAddr)); err != nil { + return fmt.Errorf("error updating KubeSpan address: %w", err) + } - kubespanCfg := cfg.TypedSpec() - mac := firstMAC.TypedSpec() + return safe.WriterModify(ctx, r, kubespan.NewIdentity(kubespan.NamespaceName, kubespan.LocalIdentity), func(res *kubespan.Identity) error { + *res.TypedSpec() = localIdentity - if err := kubespanadapter.IdentitySpec(&localIdentity).UpdateAddress(kubespanCfg.ClusterID, net.HardwareAddr(mac.HardwareAddr)); err != nil { - return fmt.Errorf("error updating KubeSpan address: %w", err) - } - - return safe.WriterModify(ctx, r, kubespan.NewIdentity(kubespan.NamespaceName, kubespan.LocalIdentity), func(res *kubespan.Identity) error { - *res.TypedSpec() = localIdentity - - return nil + return nil + }) }) } } diff --git a/internal/app/machined/pkg/controllers/network/etcfile.go b/internal/app/machined/pkg/controllers/network/etcfile.go index 2166e2eba..64f64bf8e 100644 --- a/internal/app/machined/pkg/controllers/network/etcfile.go +++ b/internal/app/machined/pkg/controllers/network/etcfile.go @@ -28,12 +28,12 @@ import ( efiles "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/files" "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" "github.com/siderolabs/talos/internal/pkg/mount/v3" - "github.com/siderolabs/talos/internal/pkg/xfs" talosconfig "github.com/siderolabs/talos/pkg/machinery/config" "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/machinery/resources/config" "github.com/siderolabs/talos/pkg/machinery/resources/files" "github.com/siderolabs/talos/pkg/machinery/resources/network" + "github.com/siderolabs/talos/pkg/xfs" ) // EtcFileController creates /etc/hostname and /etc/resolv.conf files based on finalized network configuration. diff --git a/internal/app/machined/pkg/controllers/network/etcfile_test.go b/internal/app/machined/pkg/controllers/network/etcfile_test.go index b27207497..f5a1d6f32 100644 --- a/internal/app/machined/pkg/controllers/network/etcfile_test.go +++ b/internal/app/machined/pkg/controllers/network/etcfile_test.go @@ -29,13 +29,13 @@ import ( netctrl "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/network" v1alpha1runtime "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" "github.com/siderolabs/talos/internal/pkg/mount/v3" - "github.com/siderolabs/talos/internal/pkg/xfs" - "github.com/siderolabs/talos/internal/pkg/xfs/opentree" "github.com/siderolabs/talos/pkg/machinery/config/container" "github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1" "github.com/siderolabs/talos/pkg/machinery/resources/config" "github.com/siderolabs/talos/pkg/machinery/resources/files" "github.com/siderolabs/talos/pkg/machinery/resources/network" + "github.com/siderolabs/talos/pkg/xfs" + "github.com/siderolabs/talos/pkg/xfs/opentree" ) type EtcFileConfigSuite struct { diff --git a/internal/app/machined/pkg/controllers/network/platform_config_load.go b/internal/app/machined/pkg/controllers/network/platform_config_load.go index ad2d6213a..7d73df897 100644 --- a/internal/app/machined/pkg/controllers/network/platform_config_load.go +++ b/internal/app/machined/pkg/controllers/network/platform_config_load.go @@ -6,20 +6,22 @@ package network import ( "context" + "errors" "fmt" - "os" - "path/filepath" + "io/fs" "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/safe" "go.uber.org/zap" "gopkg.in/yaml.v3" + blockadapter "github.com/siderolabs/talos/internal/app/machined/pkg/adapters/block" "github.com/siderolabs/talos/internal/app/machined/pkg/automaton" "github.com/siderolabs/talos/internal/app/machined/pkg/automaton/blockautomaton" "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/machinery/resources/block" "github.com/siderolabs/talos/pkg/machinery/resources/network" + "github.com/siderolabs/talos/pkg/xfs" ) // PlatformConfigLoadController loads cached platform network config from STATE. @@ -75,9 +77,11 @@ func (ctrl *PlatformConfigLoadController) Run(ctx context.Context, r controller. if ctrl.stateMachine == nil { ctrl.stateMachine = blockautomaton.NewVolumeMounter( - ctrl.Name(), constants.StatePartitionLabel, + ctrl.Name(), + constants.StatePartitionLabel, ctrl.load(), blockautomaton.WithReadOnly(true), + blockautomaton.WithDetached(true), ) } @@ -104,36 +108,36 @@ func (ctrl *PlatformConfigLoadController) load() func( ctx context.Context, r controller.ReaderWriter, logger *zap.Logger, mountStatus *block.VolumeMountStatus, ) error { return func(ctx context.Context, r controller.ReaderWriter, logger *zap.Logger, mountStatus *block.VolumeMountStatus) error { - rootPath := mountStatus.TypedSpec().Target - - cachedNetworkConfig, err := ctrl.loadConfig(filepath.Join(rootPath, constants.PlatformNetworkConfigFilename)) - if err != nil { - logger.Warn("ignored failure loading cached platform network config", zap.Error(err)) - } else if cachedNetworkConfig != nil { - logger.Debug("loaded cached platform network config") - } - - if cachedNetworkConfig != nil { - if err := safe.WriterModify(ctx, r, - network.NewPlatformConfig(network.NamespaceName, network.PlatformConfigCachedID), - func(out *network.PlatformConfig) error { - *out.TypedSpec() = *cachedNetworkConfig - - return nil - }, - ); err != nil { - return fmt.Errorf("error modifying cached platform network config: %w", err) + return blockadapter.VolumeMountStatus(mountStatus).WithRoot(logger, func(root xfs.Root) error { + cachedNetworkConfig, err := ctrl.loadConfig(root, constants.PlatformNetworkConfigFilename) + if err != nil { + logger.Warn("ignored failure loading cached platform network config", zap.Error(err)) + } else if cachedNetworkConfig != nil { + logger.Debug("loaded cached platform network config") } - } - return nil + if cachedNetworkConfig != nil { + if err := safe.WriterModify(ctx, r, + network.NewPlatformConfig(network.NamespaceName, network.PlatformConfigCachedID), + func(out *network.PlatformConfig) error { + *out.TypedSpec() = *cachedNetworkConfig + + return nil + }, + ); err != nil { + return fmt.Errorf("error modifying cached platform network config: %w", err) + } + } + + return nil + }) } } -func (ctrl *PlatformConfigLoadController) loadConfig(path string) (*network.PlatformConfigSpec, error) { - marshaled, err := os.ReadFile(path) +func (ctrl *PlatformConfigLoadController) loadConfig(root xfs.Root, path string) (*network.PlatformConfigSpec, error) { + marshaled, err := xfs.ReadFile(root, path) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return nil, nil } diff --git a/internal/app/machined/pkg/controllers/network/platform_config_store.go b/internal/app/machined/pkg/controllers/network/platform_config_store.go index 0cb69ee86..d2a7eef10 100644 --- a/internal/app/machined/pkg/controllers/network/platform_config_store.go +++ b/internal/app/machined/pkg/controllers/network/platform_config_store.go @@ -8,8 +8,6 @@ import ( "bytes" "context" "fmt" - "os" - "path/filepath" "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/safe" @@ -18,11 +16,13 @@ import ( "go.uber.org/zap" "gopkg.in/yaml.v3" + blockadapter "github.com/siderolabs/talos/internal/app/machined/pkg/adapters/block" "github.com/siderolabs/talos/internal/app/machined/pkg/automaton" "github.com/siderolabs/talos/internal/app/machined/pkg/automaton/blockautomaton" "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/machinery/resources/block" "github.com/siderolabs/talos/pkg/machinery/resources/network" + "github.com/siderolabs/talos/pkg/xfs" ) // PlatformConfigStoreController stores (caches) active platform network config in STATE. @@ -96,8 +96,10 @@ func (ctrl *PlatformConfigStoreController) Run(ctx context.Context, r controller if ctrl.stateMachine == nil && ctrl.configToStore != nil { ctrl.stateMachine = blockautomaton.NewVolumeMounter( - ctrl.Name(), constants.StatePartitionLabel, + ctrl.Name(), + constants.StatePartitionLabel, ctrl.store(), + blockautomaton.WithDetached(true), ) } @@ -121,34 +123,34 @@ func (ctrl *PlatformConfigStoreController) store() func( ctx context.Context, r controller.ReaderWriter, logger *zap.Logger, mountStatus *block.VolumeMountStatus, ) error { return func(ctx context.Context, r controller.ReaderWriter, logger *zap.Logger, mountStatus *block.VolumeMountStatus) error { - rootPath := mountStatus.TypedSpec().Target + return blockadapter.VolumeMountStatus(mountStatus).WithRoot(logger, func(root xfs.Root) error { + if err := ctrl.storeConfig(root, constants.PlatformNetworkConfigFilename, ctrl.configToStore); err != nil { + return fmt.Errorf("error saving platform network config: %w", err) + } - if err := ctrl.storeConfig(filepath.Join(rootPath, constants.PlatformNetworkConfigFilename), ctrl.configToStore); err != nil { - return fmt.Errorf("error saving platform network config: %w", err) - } + // remember last stored config + ctrl.lastStoredConfig, ctrl.configToStore = ctrl.configToStore, nil - // remember last stored config - ctrl.lastStoredConfig, ctrl.configToStore = ctrl.configToStore, nil + logger.Debug("stored active platform network config") - logger.Debug("stored active platform network config") - - return nil + return nil + }) } } -func (ctrl *PlatformConfigStoreController) storeConfig(path string, networkConfig *network.PlatformConfig) error { +func (ctrl *PlatformConfigStoreController) storeConfig(root xfs.Root, path string, networkConfig *network.PlatformConfig) error { marshaled, err := yaml.Marshal(networkConfig.TypedSpec()) if err != nil { return fmt.Errorf("error marshaling network config: %w", err) } - if _, err := os.Stat(path); err == nil { - existing, err := os.ReadFile(path) + if _, err := xfs.Stat(root, path); err == nil { + existing, err := xfs.ReadFile(root, path) if err == nil && bytes.Equal(marshaled, existing) { // existing contents are identical, skip writing to avoid no-op writes return nil } } - return os.WriteFile(path, marshaled, 0o400) + return xfs.WriteFile(root, path, marshaled, 0o400) } diff --git a/internal/app/machined/pkg/controllers/runtime/extension_service.go b/internal/app/machined/pkg/controllers/runtime/extension_service.go index eaa26b2dc..38d9b2f69 100644 --- a/internal/app/machined/pkg/controllers/runtime/extension_service.go +++ b/internal/app/machined/pkg/controllers/runtime/extension_service.go @@ -6,7 +6,9 @@ package runtime import ( "context" + "errors" "fmt" + "io/fs" "os" "path/filepath" @@ -72,7 +74,7 @@ func (ctrl *ExtensionServiceController) Run(ctx context.Context, r controller.Ru // extensions loading only needs to run once, as services are static serviceFiles, err := os.ReadDir(ctrl.ConfigPath) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { // directory not present, skip completely logger.Debug("extension service directory is not found") diff --git a/internal/app/machined/pkg/controllers/secrets/encryption_salt.go b/internal/app/machined/pkg/controllers/secrets/encryption_salt.go index 7f713343d..ee14152a7 100644 --- a/internal/app/machined/pkg/controllers/secrets/encryption_salt.go +++ b/internal/app/machined/pkg/controllers/secrets/encryption_salt.go @@ -12,14 +12,14 @@ import ( "github.com/cosi-project/runtime/pkg/safe" "go.uber.org/zap" + blockadapter "github.com/siderolabs/talos/internal/app/machined/pkg/adapters/block" secretsadapter "github.com/siderolabs/talos/internal/app/machined/pkg/adapters/secrets" "github.com/siderolabs/talos/internal/app/machined/pkg/automaton/blockautomaton" "github.com/siderolabs/talos/internal/app/machined/pkg/controllers" - "github.com/siderolabs/talos/internal/pkg/xfs" - "github.com/siderolabs/talos/internal/pkg/xfs/opentree" "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/machinery/resources/block" "github.com/siderolabs/talos/pkg/machinery/resources/secrets" + "github.com/siderolabs/talos/pkg/xfs" ) // EncryptionSaltController manages secrets.EncryptionSalt in STATE. @@ -74,7 +74,12 @@ func (ctrl *EncryptionSaltController) Run(ctx context.Context, r controller.Runt } if ctrl.stateMachine == nil { - ctrl.stateMachine = blockautomaton.NewVolumeMounter(ctrl.Name(), constants.StatePartitionLabel, ctrl.establishEncryptionSalt) + ctrl.stateMachine = blockautomaton.NewVolumeMounter( + ctrl.Name(), + constants.StatePartitionLabel, + ctrl.establishEncryptionSalt, + blockautomaton.WithDetached(true), + ) } if err := ctrl.stateMachine.Run(ctx, r, logger); err != nil { @@ -86,34 +91,25 @@ func (ctrl *EncryptionSaltController) Run(ctx context.Context, r controller.Runt } func (ctrl *EncryptionSaltController) establishEncryptionSalt(ctx context.Context, r controller.ReaderWriter, logger *zap.Logger, mountStatus *block.VolumeMountStatus) error { - root := &xfs.UnixRoot{FS: opentree.NewFromPath(mountStatus.TypedSpec().Target)} - if err := root.OpenFS(); err != nil { - return fmt.Errorf("error opening filesystem: %w", err) - } + return blockadapter.VolumeMountStatus(mountStatus).WithRoot(logger, func(root xfs.Root) error { + var salt secrets.EncryptionSaltSpec - defer func() { - if err := root.Close(); err != nil { - logger.Error("error closing filesystem", zap.Error(err)) + if err := controllers.LoadOrNewFromFile(root, constants.EncryptionSaltFilename, &salt, func(v *secrets.EncryptionSaltSpec) error { + return secretsadapter.EncryptionSalt(v).Generate() + }); err != nil { + return fmt.Errorf("error caching node identity: %w", err) } - }() - var salt secrets.EncryptionSaltSpec + if err := safe.WriterModify(ctx, r, secrets.NewEncryptionSalt(), func(r *secrets.EncryptionSalt) error { + *r.TypedSpec() = salt - if err := controllers.LoadOrNewFromFile(root, constants.EncryptionSaltFilename, &salt, func(v *secrets.EncryptionSaltSpec) error { - return secretsadapter.EncryptionSalt(v).Generate() - }); err != nil { - return fmt.Errorf("error caching node identity: %w", err) - } + return nil + }); err != nil { + return fmt.Errorf("error modifying resource: %w", err) + } - if err := safe.WriterModify(ctx, r, secrets.NewEncryptionSalt(), func(r *secrets.EncryptionSalt) error { - *r.TypedSpec() = salt + logger.Info("encryption salt established") return nil - }); err != nil { - return fmt.Errorf("error modifying resource: %w", err) - } - - logger.Info("encryption salt established") - - return nil + }) } diff --git a/internal/app/machined/pkg/controllers/utils.go b/internal/app/machined/pkg/controllers/utils.go index d18c083ff..7886fa8ff 100644 --- a/internal/app/machined/pkg/controllers/utils.go +++ b/internal/app/machined/pkg/controllers/utils.go @@ -8,37 +8,40 @@ package controllers import ( "errors" "fmt" + "io/fs" "os" "reflect" yaml "gopkg.in/yaml.v3" - "github.com/siderolabs/talos/internal/pkg/xfs" + "github.com/siderolabs/talos/pkg/xfs" ) // LoadOrNewFromFile either loads value from file.yaml or generates new values and saves as file.yaml. +// +//nolint:gocyclo func LoadOrNewFromFile[T any](root xfs.Root, path string, empty T, generate func(T) error) error { f, err := xfs.OpenFile(root, path, os.O_RDONLY, 0) - if err != nil && !os.IsNotExist(err) { - return fmt.Errorf("error reading state file: %w", err) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("error reading state file %q: %w", path, err) } // file doesn't exist yet, generate new value and save it - if f == nil { + if f == nil || errors.Is(err, fs.ErrNotExist) { if err = generate(empty); err != nil { return err } f, err = xfs.OpenFile(root, path, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0o600) if err != nil { - return fmt.Errorf("error creating state file: %w", err) + return fmt.Errorf("error creating state file %q: %w", path, err) } defer f.Close() //nolint:errcheck encoder := yaml.NewEncoder(f) if err = encoder.Encode(empty); err != nil { - return fmt.Errorf("error marshaling: %w", err) + return fmt.Errorf("error marshaling %q: %w", path, err) } if err = encoder.Close(); err != nil { @@ -52,11 +55,11 @@ func LoadOrNewFromFile[T any](root xfs.Root, path string, empty T, generate func defer f.Close() //nolint:errcheck if err = yaml.NewDecoder(f).Decode(empty); err != nil { - return fmt.Errorf("error unmarshaling: %w", err) + return fmt.Errorf("error unmarshaling %q: %w", path, err) } if reflect.ValueOf(empty).Elem().IsZero() { - return errors.New("value is still zero after unmarshaling") + return fmt.Errorf("value of %q is still zero after unmarshaling", path) } return f.Close() diff --git a/internal/app/machined/pkg/runtime/kernel_linux.go b/internal/app/machined/pkg/runtime/kernel_linux.go index fdcaa0318..a6c2077e2 100644 --- a/internal/app/machined/pkg/runtime/kernel_linux.go +++ b/internal/app/machined/pkg/runtime/kernel_linux.go @@ -13,8 +13,8 @@ import ( "golang.org/x/sys/unix" - "github.com/siderolabs/talos/internal/pkg/xfs/fsopen" - "github.com/siderolabs/talos/internal/pkg/xfs/opentree" + "github.com/siderolabs/talos/pkg/xfs/fsopen" + "github.com/siderolabs/talos/pkg/xfs/opentree" ) // KernelCap represents kernel capabilities that we can check at runtime. diff --git a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/mount/mount.go b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/mount/mount.go index 450e9d228..ed6daaf62 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/mount/mount.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/mount/mount.go @@ -15,7 +15,7 @@ import ( "github.com/siderolabs/go-pointer" "github.com/siderolabs/talos/internal/pkg/mount/v3" - "github.com/siderolabs/talos/internal/pkg/xfs/fsopen" + "github.com/siderolabs/talos/pkg/xfs/fsopen" ) // Spec specifies what has to be mounted. diff --git a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/sdboot/sdboot.go b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/sdboot/sdboot.go index 0d5738a99..931c9e1ce 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/bootloader/sdboot/sdboot.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/bootloader/sdboot/sdboot.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "io/fs" "log" "os" "path/filepath" @@ -340,7 +341,7 @@ func sdBootFilePath(arch string) (string, error) { //nolint:gocyclo,cyclop func (c *Config) install(opts options.InstallOptions) (*options.InstallResult, error) { if _, err := os.Stat(filepath.Join(opts.MountPrefix, constants.EFIMountPoint, "loader", "loader.conf")); err != nil { - if !os.IsNotExist(err) { + if !errors.Is(err, fs.ErrNotExist) { return nil, err } diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/azure/azure.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/azure/azure.go index dc536fa3f..d1d0028e7 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/azure/azure.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/azure/azure.go @@ -12,6 +12,7 @@ import ( "encoding/xml" stderrors "errors" "fmt" + "io/fs" "log" "net/netip" "os" @@ -272,7 +273,7 @@ func (a *Azure) configFromCD() ([]byte, error) { ovfEnvFile, err := os.ReadFile(filepath.Join(mnt, "ovf-env.xml")) if err != nil { // Device mount worked, but it wasn't the "CD" that contains the xml file - if os.IsNotExist(err) { + if stderrors.Is(err, fs.ErrNotExist) { continue } diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/metal.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/metal.go index 36d70eee3..7d00df20e 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/metal.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/metal.go @@ -7,7 +7,9 @@ package metal import ( "context" + stderrors "errors" "fmt" + "io/fs" "log" "os" "path/filepath" @@ -28,7 +30,6 @@ import ( "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/oauth2" "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/url" "github.com/siderolabs/talos/internal/pkg/mount/v3" - "github.com/siderolabs/talos/internal/pkg/xfs/fsopen" "github.com/siderolabs/talos/pkg/download" "github.com/siderolabs/talos/pkg/machinery/cel" "github.com/siderolabs/talos/pkg/machinery/cel/celenv" @@ -38,6 +39,7 @@ import ( "github.com/siderolabs/talos/pkg/machinery/resources/block" "github.com/siderolabs/talos/pkg/machinery/resources/hardware" runtimeres "github.com/siderolabs/talos/pkg/machinery/resources/runtime" + "github.com/siderolabs/talos/pkg/xfs/fsopen" ) const ( @@ -89,7 +91,7 @@ func (m *Metal) Configuration(ctx context.Context, r state.State) ([]byte, error } oauth2Cfg, err := oauth2.NewConfig(procfs.ProcCmdline(), *option) - if err != nil && !os.IsNotExist(err) { + if err != nil && !stderrors.Is(err, fs.ErrNotExist) { return nil, fmt.Errorf("failed to parse OAuth2 config: %w", err) } @@ -180,7 +182,6 @@ func readConfigFromISO(ctx context.Context, r state.State) ([]byte, error) { mount.WithFsopen( volumeStatus.TypedSpec().Filesystem.String(), fsopen.WithSource(volumeStatus.TypedSpec().MountLocation), - fsopen.WithPrinter(log.Printf), ), ) diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/oauth2/oauth2_test.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/oauth2/oauth2_test.go index 04d76e6cb..04e8c2e53 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/oauth2/oauth2_test.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/oauth2/oauth2_test.go @@ -6,9 +6,10 @@ package oauth2_test import ( "context" + "errors" + "io/fs" "net/http" "net/http/httptest" - "os" "testing" "time" @@ -70,7 +71,7 @@ func TestNewConfig(t *testing.T) { //nolint:tparallel cfg, err := oauth2.NewConfig(procfs.NewCmdline(test.cmdline), "https://example.com/my/config") if test.expected == nil { require.Error(t, err) - assert.True(t, os.IsNotExist(err)) + assert.True(t, errors.Is(err, fs.ErrNotExist)) return } 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 73642aa68..d5be7af8b 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go @@ -13,6 +13,7 @@ import ( "encoding/json" "errors" "fmt" + "io/fs" "log" "os" "path/filepath" @@ -84,7 +85,7 @@ func WaitForUSB(runtime.Sequence, any) (runtime.TaskExecutionFunc, string) { _, err := os.Stat(file) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return nil } @@ -716,7 +717,7 @@ func existsAndIsFile(p string) (err error) { info, err = os.Stat(p) if err != nil { - if !os.IsNotExist(err) { + if !errors.Is(err, fs.ErrNotExist) { return err } @@ -829,7 +830,7 @@ func CordonAndDrainNode(runtime.Sequence, any) (runtime.TaskExecutionFunc, strin return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) { // skip not exist error as it means that the node hasn't fully joined yet if _, err = os.Stat("/var/lib/kubelet/pki/kubelet-client-current.pem"); err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return nil } @@ -893,7 +894,7 @@ func LeaveEtcd(runtime.Sequence, any) (runtime.TaskExecutionFunc, string) { return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) { _, err = os.Stat(filepath.Join(constants.EtcdDataPath, "/member")) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return nil } @@ -1002,7 +1003,7 @@ func stopAndRemoveAllPods(stopAction cri.StopAction) runtime.TaskExecutionFunc { } // check that the CRI is running and the socket is available, if not, skip the rest - if _, err = os.Stat(constants.CRIContainerdAddress); os.IsNotExist(err) { + if _, err = os.Stat(constants.CRIContainerdAddress); errors.Is(err, fs.ErrNotExist) { return nil } @@ -1717,12 +1718,12 @@ func ForceCleanup(runtime.Sequence, any) (runtime.TaskExecutionFunc, string) { func ReloadMeta(runtime.Sequence, any) (runtime.TaskExecutionFunc, string) { return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) error { err := r.State().Machine().Meta().Reload(ctx) - if err != nil && !os.IsNotExist(err) { + if err != nil && !errors.Is(err, fs.ErrNotExist) { return err } // attempt to populate meta from the environment if Talos is not installed (yet) - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { env := environment.Get(r.Config()) prefix := constants.MetaValuesEnvVar + "=" diff --git a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go index 0b87f7ef4..14e3aa8d7 100644 --- a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go +++ b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go @@ -40,12 +40,12 @@ import ( "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" runtimelogging "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/logging" "github.com/siderolabs/talos/internal/app/machined/pkg/system" - "github.com/siderolabs/talos/internal/pkg/xfs" - "github.com/siderolabs/talos/internal/pkg/xfs/fsopen" "github.com/siderolabs/talos/pkg/logging" talosconfig "github.com/siderolabs/talos/pkg/machinery/config/config" "github.com/siderolabs/talos/pkg/machinery/constants" configresource "github.com/siderolabs/talos/pkg/machinery/resources/config" + "github.com/siderolabs/talos/pkg/xfs" + "github.com/siderolabs/talos/pkg/xfs/fsopen" ) // Controller implements runtime.V1alpha2Controller. diff --git a/internal/app/machined/pkg/system/runner/containerd/containerd.go b/internal/app/machined/pkg/system/runner/containerd/containerd.go index e7b18675e..c92cd3fd0 100644 --- a/internal/app/machined/pkg/system/runner/containerd/containerd.go +++ b/internal/app/machined/pkg/system/runner/containerd/containerd.go @@ -7,10 +7,11 @@ package containerd import ( "bytes" "context" + "errors" "fmt" "io" + "io/fs" "log" - "os" "syscall" "time" @@ -180,7 +181,7 @@ func (c *containerdRunner) Run(eventSink events.Recorder) error { // if one still exists defer func() { err := cg.Delete() - if err != nil && !os.IsNotExist(err) { + if err != nil && !errors.Is(err, fs.ErrNotExist) { eventSink(events.StateStopping, "Failed to remove cgroup for %s, %s", c, err) } }() diff --git a/internal/app/machined/pkg/system/services/registry/readers.go b/internal/app/machined/pkg/system/services/registry/readers.go index 6c1252933..5da259eb9 100644 --- a/internal/app/machined/pkg/system/services/registry/readers.go +++ b/internal/app/machined/pkg/system/services/registry/readers.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "io/fs" - "os" "github.com/containerd/containerd/v2/core/content" "github.com/containerd/errdefs" @@ -95,7 +94,7 @@ func (r *readSeeker) Seek(offset int64, whence int) (int64, error) { func openReaderAt(p string, statFS fs.StatFS) (content.ReaderAt, error) { fi, err := statFS.Stat(p) if err != nil { - if !os.IsNotExist(err) { + if !errors.Is(err, fs.ErrNotExist) { return nil, err } @@ -104,7 +103,7 @@ func openReaderAt(p string, statFS fs.StatFS) (content.ReaderAt, error) { fp, err := statFS.Open(p) if err != nil { - if !os.IsNotExist(err) { + if !errors.Is(err, fs.ErrNotExist) { return nil, err } diff --git a/internal/app/machined/pkg/system/services/registry/store.go b/internal/app/machined/pkg/system/services/registry/store.go index d45dea5f1..9c9b50f21 100644 --- a/internal/app/machined/pkg/system/services/registry/store.go +++ b/internal/app/machined/pkg/system/services/registry/store.go @@ -36,7 +36,7 @@ func (s *singleFileStore) Info(_ context.Context, dgst digest.Digest) (content.I fi, err := s.root.Stat(p) if err != nil { - if os.IsNotExist(err) || errors.Is(err, errdefs.ErrNotFound) { + if errors.Is(err, fs.ErrNotExist) || errors.Is(err, errdefs.ErrNotFound) { return content.Info{}, xerrors.NewTaggedf[notFoundTag]("content '%s': %w", dgst, errdefs.ErrNotFound) } diff --git a/internal/app/machined/revert.go b/internal/app/machined/revert.go index ef96c48c8..091462f16 100644 --- a/internal/app/machined/revert.go +++ b/internal/app/machined/revert.go @@ -6,9 +6,10 @@ package main import ( "context" + "errors" "fmt" + "io/fs" "log" - "os" "github.com/cosi-project/runtime/pkg/state" @@ -52,7 +53,7 @@ func revertBootloadInternal(ctx context.Context, resourceState state.State) erro metaState, err := meta.New(ctx, resourceState) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { // no META, no way to revert return nil } diff --git a/internal/pkg/extensions/kernel_modules.go b/internal/pkg/extensions/kernel_modules.go index 58ab0fbab..7721c42fc 100644 --- a/internal/pkg/extensions/kernel_modules.go +++ b/internal/pkg/extensions/kernel_modules.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "io" + "io/fs" "log" "os" "os/exec" @@ -28,7 +29,7 @@ import ( // ProvidesKernelModules returns true if the extension provides kernel modules. func (ext *Extension) ProvidesKernelModules(quirks quirks.Quirks) bool { - if _, err := os.Stat(ext.KernelModuleDirectory(quirks)); os.IsNotExist(err) { + if _, err := os.Stat(ext.KernelModuleDirectory(quirks)); errors.Is(err, fs.ErrNotExist) { return false } diff --git a/internal/pkg/extensions/list.go b/internal/pkg/extensions/list.go index 61c117a16..8edecdf09 100644 --- a/internal/pkg/extensions/list.go +++ b/internal/pkg/extensions/list.go @@ -6,7 +6,9 @@ package extensions import ( "cmp" + "errors" "fmt" + "io/fs" "os" "path/filepath" "slices" @@ -18,7 +20,7 @@ import ( func List(rootPath string) ([]*Extension, error) { items, err := os.ReadDir(rootPath) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return nil, nil } diff --git a/internal/pkg/mount/v3/helpers.go b/internal/pkg/mount/v3/helpers.go index 58e07a148..ac5505275 100644 --- a/internal/pkg/mount/v3/helpers.go +++ b/internal/pkg/mount/v3/helpers.go @@ -15,8 +15,8 @@ import ( "golang.org/x/sys/unix" "github.com/siderolabs/talos/internal/pkg/selinux" - "github.com/siderolabs/talos/internal/pkg/xfs/fsopen" "github.com/siderolabs/talos/pkg/machinery/constants" + "github.com/siderolabs/talos/pkg/xfs/fsopen" ) // @@ -40,9 +40,7 @@ func NewCgroup2() *Manager { // NewReadOnlyOverlay creates a new read-only overlay filesystem. func NewReadOnlyOverlay(sources []string, target string, printer func(string, ...any), options ...ManagerOption) *Manager { - fsOptions := []fsopen.Option{ - fsopen.WithPrinter(printer), - } + fsOptions := []fsopen.Option{} if printer != nil { printer("mounting %d overlays: %v", len(sources), sources) @@ -58,8 +56,7 @@ func NewReadOnlyOverlay(sources []string, target string, printer func(string, .. options = append(options, WithTarget(target), - WithPrinter(printer), - WithMountAttributes(unix.MOUNT_ATTR_RDONLY), + WithReadOnly(), WithFsopen("overlay", fsOptions...), ) @@ -75,7 +72,6 @@ func NewOverlayWithBasePath(sources []string, target, basePath string, printer f workdir := fmt.Sprintf(filepath.Join(basePath, "%s-workdir"), overlayPrefix) fsOptions := []fsopen.Option{ - fsopen.WithPrinter(printer), fsopen.WithStringParameter("upperdir", diff), fsopen.WithStringParameter("workdir", workdir), } @@ -127,7 +123,6 @@ func Squashfs(target, squashfsFile string, printer func(string, ...any)) (*Manag "squashfs", fsopen.WithSource(dev.Path()), fsopen.WithBoolParameter("ro"), - fsopen.WithPrinter(printer), ), ), nil } @@ -184,7 +179,6 @@ func Pseudo(printer func(string, ...any)) Managers { WithFsopen( "devtmpfs", fsopen.WithStringParameter("mode", "0755"), - fsopen.WithPrinter(printer), ), ), newManager( @@ -192,13 +186,13 @@ func Pseudo(printer func(string, ...any)) Managers { WithTarget("/proc"), WithKeepOpenAfterMount(), WithMountAttributes(unix.MOUNT_ATTR_NOSUID|unix.MOUNT_ATTR_NOEXEC|unix.MOUNT_ATTR_NODEV), - WithFsopen("proc", fsopen.WithPrinter(printer)), + WithFsopen("proc"), ), newManager( always, WithTarget("/sys"), WithKeepOpenAfterMount(), - WithFsopen("sysfs", fsopen.WithPrinter(printer)), + WithFsopen("sysfs"), ), ) } @@ -213,7 +207,6 @@ func PseudoLate(printer func(string, ...any)) Managers { WithSelinuxLabel(constants.RunSelinuxLabel), WithFsopen( "tmpfs", - fsopen.WithPrinter(printer), fsopen.WithStringParameter("mode", "0755"), ), ), @@ -223,7 +216,6 @@ func PseudoLate(printer func(string, ...any)) Managers { WithSelinuxLabel(constants.SystemSelinuxLabel), WithFsopen( "tmpfs", - fsopen.WithPrinter(printer), fsopen.WithStringParameter("mode", "0755"), ), ), @@ -233,7 +225,6 @@ func PseudoLate(printer func(string, ...any)) Managers { WithMountAttributes(unix.MOUNT_ATTR_NOSUID|unix.MOUNT_ATTR_NOEXEC|unix.MOUNT_ATTR_NODEV), WithFsopen( "tmpfs", - fsopen.WithPrinter(printer), fsopen.WithStringParameter("mode", "0755"), fsopen.WithStringParameter("size", "64M"), ), @@ -248,7 +239,7 @@ func PseudoSub(printer func(string, ...any)) Managers { always, WithTarget("/dev/shm"), WithMountAttributes(unix.MOUNT_ATTR_NOSUID|unix.MOUNT_ATTR_NOEXEC|unix.MOUNT_ATTR_NODEV|unix.MOUNT_ATTR_RELATIME), - WithFsopen("tmpfs", fsopen.WithPrinter(printer)), + WithFsopen("tmpfs"), ), newManager( always, @@ -259,55 +250,54 @@ func PseudoSub(printer func(string, ...any)) Managers { fsopen.WithStringParameter("ptmxmode", "000"), fsopen.WithStringParameter("mode", "620"), fsopen.WithStringParameter("gid", "5"), - fsopen.WithPrinter(printer), ), ), newManager( always, WithMountAttributes(unix.MOUNT_ATTR_NOSUID|unix.MOUNT_ATTR_NODEV), WithTarget("/dev/hugepages"), - WithFsopen("hugetlbfs", fsopen.WithPrinter(printer)), + WithFsopen("hugetlbfs"), ), newManager( always, WithTarget("/sys/fs/bpf"), - WithFsopen("bpf", fsopen.WithPrinter(printer)), + WithFsopen("bpf"), ), newManager( always, WithTarget("/sys/kernel/security"), WithMountAttributes(unix.MOUNT_ATTR_NOSUID|unix.MOUNT_ATTR_NOEXEC|unix.MOUNT_ATTR_NODEV|unix.MOUNT_ATTR_RELATIME), - WithFsopen("securityfs", fsopen.WithPrinter(printer)), + WithFsopen("securityfs"), ), newManager( always, WithTarget("/sys/kernel/tracing"), WithMountAttributes(unix.MOUNT_ATTR_NOSUID|unix.MOUNT_ATTR_NOEXEC|unix.MOUNT_ATTR_NODEV), - WithFsopen("tracefs", fsopen.WithPrinter(printer)), + WithFsopen("tracefs"), ), newManager( always, WithTarget("/sys/kernel/config"), WithMountAttributes(unix.MOUNT_ATTR_NOSUID|unix.MOUNT_ATTR_NOEXEC|unix.MOUNT_ATTR_NODEV|unix.MOUNT_ATTR_RELATIME), - WithFsopen("configfs", fsopen.WithPrinter(printer)), + WithFsopen("configfs"), ), newManager( always, WithTarget("/sys/kernel/debug"), WithMountAttributes(unix.MOUNT_ATTR_NOSUID|unix.MOUNT_ATTR_NOEXEC|unix.MOUNT_ATTR_NODEV|unix.MOUNT_ATTR_RELATIME), - WithFsopen("debugfs", fsopen.WithPrinter(printer)), + WithFsopen("debugfs"), ), newManager( selinux.IsEnabled, WithTarget("/sys/fs/selinux"), WithMountAttributes(unix.MOUNT_ATTR_NOSUID|unix.MOUNT_ATTR_NOEXEC|unix.MOUNT_ATTR_RELATIME), - WithFsopen("selinuxfs", fsopen.WithPrinter(printer)), + WithFsopen("selinuxfs"), ), newManager( hasEFIVars, WithTarget(constants.EFIVarsMountPoint), WithMountAttributes(unix.MOUNT_ATTR_NOSUID|unix.MOUNT_ATTR_NOEXEC|unix.MOUNT_ATTR_NODEV|unix.MOUNT_ATTR_RELATIME|unix.MOUNT_ATTR_RDONLY), - WithFsopen("efivarfs", fsopen.WithPrinter(printer)), + WithFsopen("efivarfs"), ), ) } diff --git a/internal/pkg/mount/v3/manager.go b/internal/pkg/mount/v3/manager.go index b5bf39179..a531de348 100644 --- a/internal/pkg/mount/v3/manager.go +++ b/internal/pkg/mount/v3/manager.go @@ -11,8 +11,8 @@ import ( "golang.org/x/sys/unix" - "github.com/siderolabs/talos/internal/pkg/xfs" - "github.com/siderolabs/talos/internal/pkg/xfs/fsopen" + "github.com/siderolabs/talos/pkg/xfs" + "github.com/siderolabs/talos/pkg/xfs/fsopen" ) // Manager is the filesystem manager for mounting and unmounting filesystems. @@ -26,6 +26,7 @@ type Manager struct { shared bool skipIfMounted bool keepOpen bool + detached bool mountattr int extraDirs []string extraUnmountCallbacks []func(m *Manager) @@ -71,6 +72,7 @@ func (m *Manager) Mount() (*Point, error) { m.point = &Point{ root: root, + detached: m.detached, keepOpen: m.keepOpen, target: m.target, selinuxLabel: m.selinuxLabel, @@ -83,8 +85,6 @@ func (m *Manager) Mount() (*Point, error) { MountAttributes: m.mountattr, } - printer("mkdirAll %q", m.target) - if err := os.MkdirAll(m.target, 0o755); err != nil { return nil, fmt.Errorf("failed to create mount target %s: %w", m.target, err) } @@ -207,6 +207,16 @@ func WithReadOnly() ManagerOption { } } +// WithDetached sets the mount as detached. +func WithDetached() ManagerOption { + return ManagerOption{ + set: func(m *Manager) { + m.detached = true + m.keepOpen = true + }, + } +} + // WithExtraDirs adds extra dirs to the manager that should be created prior to mounting the filesystem. func WithExtraDirs(dirs ...string) ManagerOption { return ManagerOption{ diff --git a/internal/pkg/mount/v3/point.go b/internal/pkg/mount/v3/point.go index eef714167..ef65eb1ec 100644 --- a/internal/pkg/mount/v3/point.go +++ b/internal/pkg/mount/v3/point.go @@ -11,19 +11,21 @@ import ( "fmt" "os" "strings" + "syscall" "time" "github.com/siderolabs/go-retry/retry" "golang.org/x/sys/unix" "github.com/siderolabs/talos/internal/pkg/selinux" - "github.com/siderolabs/talos/internal/pkg/xfs" + "github.com/siderolabs/talos/pkg/xfs" ) // Point represents a mount point in the filesystem. type Point struct { root xfs.Root keepOpen bool + detached bool fstype string source string target string @@ -62,6 +64,10 @@ func (p *Point) Mount(opts Options) error { defer p.Release(false) //nolint:errcheck + if p.detached { + return nil + } + return p.retry(func() error { if err := p.moveMount(p.target); err != nil { return fmt.Errorf("error mounting %q to %q: %w", p.Source(), p.target, err) @@ -85,6 +91,10 @@ func (p *Point) Mount(opts Options) error { // Share makes the mount point shared. func (p *Point) Share() error { + if p.detached { + return syscall.EINVAL + } + return p.setattr(&unix.MountAttr{ Propagation: unix.MS_SHARED, }, unix.AT_RECURSIVE) @@ -110,6 +120,10 @@ func (p *Point) Unmount(opts UnmountOptions) error { return err } + if p.detached { + return nil + } + return p.retry(func() error { return SafeUnmount(context.Background(), opts.Printer, p.target) }, true) @@ -237,8 +251,17 @@ func (p *Point) FSType() string { return p.fstype } +// Root returns the underlying xfs.Root of the mount point. +func (p *Point) Root() xfs.Root { + return p.root +} + // RemountReadOnly remounts the mount point as read-only. func (p *Point) RemountReadOnly() error { + if p.detached { + return syscall.EINVAL + } + return p.setattr(&unix.MountAttr{ Attr_set: unix.MOUNT_ATTR_RDONLY, }, 0) @@ -246,6 +269,10 @@ func (p *Point) RemountReadOnly() error { // RemountReadWrite remounts the mount point as read-write. func (p *Point) RemountReadWrite() error { + if p.detached { + return nil + } + return p.setattr(&unix.MountAttr{ Attr_clr: unix.MOUNT_ATTR_RDONLY, }, 0) diff --git a/internal/pkg/rng/tpm.go b/internal/pkg/rng/tpm.go index 5f7c34d8b..8700f609d 100644 --- a/internal/pkg/rng/tpm.go +++ b/internal/pkg/rng/tpm.go @@ -5,9 +5,10 @@ package rng import ( + "errors" "fmt" + "io/fs" "log" - "os" "time" "github.com/google/go-tpm/tpm2" @@ -22,7 +23,7 @@ func TPMSeed() error { t, err := tpm.Open() if err != nil { // if the TPM is not available we can skip seeding random pool - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { log.Printf("TPM device is not available") return nil diff --git a/internal/pkg/secureboot/tpm2/pcr.go b/internal/pkg/secureboot/tpm2/pcr.go index a657afd6c..d4cfe709e 100644 --- a/internal/pkg/secureboot/tpm2/pcr.go +++ b/internal/pkg/secureboot/tpm2/pcr.go @@ -8,9 +8,10 @@ package tpm2 import ( "bytes" "crypto/sha256" + "errors" "fmt" + "io/fs" "log" - "os" "github.com/google/go-tpm/tpm2" "github.com/google/go-tpm/tpm2/transport" @@ -67,7 +68,7 @@ func PCRExtend(pcr int, data []byte) error { t, err := tpm.Open() if err != nil { // if the TPM is not available we can skip the PCR extension - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { log.Printf("TPM device is not available, skipping PCR extension") return nil diff --git a/internal/pkg/selinux/selinux.go b/internal/pkg/selinux/selinux.go index 529db42a7..705ad828a 100644 --- a/internal/pkg/selinux/selinux.go +++ b/internal/pkg/selinux/selinux.go @@ -19,8 +19,8 @@ import ( "golang.org/x/sys/unix" "github.com/siderolabs/talos/internal/pkg/containermode" - "github.com/siderolabs/talos/internal/pkg/xfs" "github.com/siderolabs/talos/pkg/machinery/constants" + "github.com/siderolabs/talos/pkg/xfs" ) //go:embed policy/policy.33 diff --git a/pkg/conditions/files.go b/pkg/conditions/files.go index 4f22c9008..5271e8556 100644 --- a/pkg/conditions/files.go +++ b/pkg/conditions/files.go @@ -6,7 +6,9 @@ package conditions import ( "context" + "errors" "fmt" + "io/fs" "os" "time" @@ -25,7 +27,7 @@ func (filename file) Wait(ctx context.Context) error { return nil } - if !os.IsNotExist(err) { + if !errors.Is(err, fs.ErrNotExist) { return err } diff --git a/pkg/conditions/kubeconfig.go b/pkg/conditions/kubeconfig.go index 9ae8394ca..296a82da3 100644 --- a/pkg/conditions/kubeconfig.go +++ b/pkg/conditions/kubeconfig.go @@ -6,7 +6,9 @@ package conditions import ( "context" + "errors" "fmt" + "io/fs" "os" "time" @@ -21,7 +23,7 @@ func (filename kubeconfig) Wait(ctx context.Context) error { for { _, err := os.Stat(string(filename)) - if err != nil && !os.IsNotExist(err) { + if err != nil && !errors.Is(err, fs.ErrNotExist) { return err } diff --git a/pkg/imager/profile/profile_test.go b/pkg/imager/profile/profile_test.go index 023874908..b371ff1fa 100644 --- a/pkg/imager/profile/profile_test.go +++ b/pkg/imager/profile/profile_test.go @@ -5,6 +5,8 @@ package profile_test import ( + "errors" + "io/fs" "os" "sort" "strings" @@ -79,7 +81,7 @@ func TestFillDefaults(t *testing.T) { require.NoError(t, p.Dump(&profileData)) expectedData, err := os.ReadFile("testdata/" + profile + "-" + arch + "-" + version + ".yaml") - if os.IsNotExist(err) && recordMissing { + if errors.Is(err, fs.ErrNotExist) && recordMissing { require.NoError(t, os.WriteFile("testdata/"+profile+"-"+arch+"-"+version+".yaml", []byte(profileData.String()), 0o644)) } else { require.NoError(t, err) diff --git a/pkg/kubernetes/kubelet/kubelet.go b/pkg/kubernetes/kubelet/kubelet.go index bfeea155c..b0022ef9a 100644 --- a/pkg/kubernetes/kubelet/kubelet.go +++ b/pkg/kubernetes/kubelet/kubelet.go @@ -8,7 +8,9 @@ package kubelet import ( "context" "encoding/json" + "errors" "fmt" + "io/fs" "os" "path/filepath" "time" @@ -49,7 +51,7 @@ func NewClient(nodename string, clientCert, clientKey, caPEM []byte) (*Client, e config.CAData = append(config.CAData, kubeletCert...) } else if err != nil { // ignore if file doesn't exist, assume cert isn't self-signed - if !os.IsNotExist(err) { + if !errors.Is(err, fs.ErrNotExist) { return nil, fmt.Errorf("error reading kubelet certificate: %w", err) } } diff --git a/pkg/machinery/api/resource/definitions/block/block.pb.go b/pkg/machinery/api/resource/definitions/block/block.pb.go index 24eaf3a56..05ac93ead 100644 --- a/pkg/machinery/api/resource/definitions/block/block.pb.go +++ b/pkg/machinery/api/resource/definitions/block/block.pb.go @@ -931,6 +931,7 @@ type MountRequestSpec struct { Requesters []string `protobuf:"bytes,3,rep,name=requesters,proto3" json:"requesters,omitempty"` RequesterIDs []string `protobuf:"bytes,4,rep,name=requester_i_ds,json=requesterIDs,proto3" json:"requester_i_ds,omitempty"` ReadOnly bool `protobuf:"varint,5,opt,name=read_only,json=readOnly,proto3" json:"read_only,omitempty"` + Detached bool `protobuf:"varint,6,opt,name=detached,proto3" json:"detached,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1000,6 +1001,13 @@ func (x *MountRequestSpec) GetReadOnly() bool { return false } +func (x *MountRequestSpec) GetDetached() bool { + if x != nil { + return x.Detached + } + return false +} + // MountSpec is the spec for volume mount. type MountSpec struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1111,6 +1119,7 @@ type MountStatusSpec struct { ReadOnly bool `protobuf:"varint,5,opt,name=read_only,json=readOnly,proto3" json:"read_only,omitempty"` ProjectQuotaSupport bool `protobuf:"varint,6,opt,name=project_quota_support,json=projectQuotaSupport,proto3" json:"project_quota_support,omitempty"` EncryptionProvider enums.BlockEncryptionProviderType `protobuf:"varint,7,opt,name=encryption_provider,json=encryptionProvider,proto3,enum=talos.resource.definitions.enums.BlockEncryptionProviderType" json:"encryption_provider,omitempty"` + Detached bool `protobuf:"varint,8,opt,name=detached,proto3" json:"detached,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1194,6 +1203,13 @@ func (x *MountStatusSpec) GetEncryptionProvider() enums.BlockEncryptionProviderT return enums.BlockEncryptionProviderType(0) } +func (x *MountStatusSpec) GetDetached() bool { + if x != nil { + return x.Detached + } + return false +} + // PartitionSpec is the spec for volume partitioning. type PartitionSpec struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1789,6 +1805,7 @@ type VolumeMountRequestSpec struct { VolumeId string `protobuf:"bytes,1,opt,name=volume_id,json=volumeId,proto3" json:"volume_id,omitempty"` Requester string `protobuf:"bytes,2,opt,name=requester,proto3" json:"requester,omitempty"` ReadOnly bool `protobuf:"varint,3,opt,name=read_only,json=readOnly,proto3" json:"read_only,omitempty"` + Detached bool `protobuf:"varint,4,opt,name=detached,proto3" json:"detached,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1844,6 +1861,13 @@ func (x *VolumeMountRequestSpec) GetReadOnly() bool { return false } +func (x *VolumeMountRequestSpec) GetDetached() bool { + if x != nil { + return x.Detached + } + return false +} + // VolumeMountStatusSpec is the spec for VolumeMountStatus. type VolumeMountStatusSpec struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1851,6 +1875,7 @@ type VolumeMountStatusSpec struct { Requester string `protobuf:"bytes,2,opt,name=requester,proto3" json:"requester,omitempty"` Target string `protobuf:"bytes,3,opt,name=target,proto3" json:"target,omitempty"` ReadOnly bool `protobuf:"varint,4,opt,name=read_only,json=readOnly,proto3" json:"read_only,omitempty"` + Detached bool `protobuf:"varint,5,opt,name=detached,proto3" json:"detached,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1913,6 +1938,13 @@ func (x *VolumeMountStatusSpec) GetReadOnly() bool { return false } +func (x *VolumeMountStatusSpec) GetDetached() bool { + if x != nil { + return x.Detached + } + return false +} + // VolumeStatusSpec is the spec for VolumeStatus resource. type VolumeStatusSpec struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -2340,7 +2372,7 @@ const file_resource_definitions_block_block_proto_rawDesc = "" + "\x04type\x18\x01 \x01(\x0e25.talos.resource.definitions.enums.BlockFilesystemTypeR\x04type\x12\x14\n" + "\x05label\x18\x02 \x01(\tR\x05label\"J\n" + "\vLocatorSpec\x12;\n" + - "\x05match\x18\x01 \x01(\v2%.google.api.expr.v1alpha1.CheckedExprR\x05match\"\xba\x01\n" + + "\x05match\x18\x01 \x01(\v2%.google.api.expr.v1alpha1.CheckedExprR\x05match\"\xd6\x01\n" + "\x10MountRequestSpec\x12\x1b\n" + "\tvolume_id\x18\x01 \x01(\tR\bvolumeId\x12&\n" + "\x0fparent_mount_id\x18\x02 \x01(\tR\rparentMountId\x12\x1e\n" + @@ -2348,7 +2380,8 @@ const file_resource_definitions_block_block_proto_rawDesc = "" + "requesters\x18\x03 \x03(\tR\n" + "requesters\x12$\n" + "\x0erequester_i_ds\x18\x04 \x03(\tR\frequesterIDs\x12\x1b\n" + - "\tread_only\x18\x05 \x01(\bR\breadOnly\"\x90\x02\n" + + "\tread_only\x18\x05 \x01(\bR\breadOnly\x12\x1a\n" + + "\bdetached\x18\x06 \x01(\bR\bdetached\"\x90\x02\n" + "\tMountSpec\x12\x1f\n" + "\vtarget_path\x18\x01 \x01(\tR\n" + "targetPath\x12#\n" + @@ -2358,7 +2391,7 @@ const file_resource_definitions_block_block_proto_rawDesc = "" + "\tfile_mode\x18\x05 \x01(\rR\bfileMode\x12\x10\n" + "\x03uid\x18\x06 \x01(\x03R\x03uid\x12\x10\n" + "\x03gid\x18\a \x01(\x03R\x03gid\x12+\n" + - "\x11recursive_relabel\x18\b \x01(\bR\x10recursiveRelabel\"\xa1\x03\n" + + "\x11recursive_relabel\x18\b \x01(\bR\x10recursiveRelabel\"\xbd\x03\n" + "\x0fMountStatusSpec\x12F\n" + "\x04spec\x18\x01 \x01(\v22.talos.resource.definitions.block.MountRequestSpecR\x04spec\x12\x16\n" + "\x06target\x18\x02 \x01(\tR\x06target\x12\x16\n" + @@ -2368,7 +2401,8 @@ const file_resource_definitions_block_block_proto_rawDesc = "" + "filesystem\x12\x1b\n" + "\tread_only\x18\x05 \x01(\bR\breadOnly\x122\n" + "\x15project_quota_support\x18\x06 \x01(\bR\x13projectQuotaSupport\x12n\n" + - "\x13encryption_provider\x18\a \x01(\x0e2=.talos.resource.definitions.enums.BlockEncryptionProviderTypeR\x12encryptionProvider\"\x8c\x01\n" + + "\x13encryption_provider\x18\a \x01(\x0e2=.talos.resource.definitions.enums.BlockEncryptionProviderTypeR\x12encryptionProvider\x12\x1a\n" + + "\bdetached\x18\b \x01(\bR\bdetached\"\x8c\x01\n" + "\rPartitionSpec\x12\x19\n" + "\bmin_size\x18\x01 \x01(\x04R\aminSize\x12\x19\n" + "\bmax_size\x18\x02 \x01(\x04R\amaxSize\x12\x12\n" + @@ -2416,16 +2450,18 @@ const file_resource_definitions_block_block_proto_rawDesc = "" + "\n" + "encryption\x18\x06 \x01(\v20.talos.resource.definitions.block.EncryptionSpecR\n" + "encryption\x12S\n" + - "\asymlink\x18\a \x01(\v29.talos.resource.definitions.block.SymlinkProvisioningSpecR\asymlink\"p\n" + + "\asymlink\x18\a \x01(\v29.talos.resource.definitions.block.SymlinkProvisioningSpecR\asymlink\"\x8c\x01\n" + "\x16VolumeMountRequestSpec\x12\x1b\n" + "\tvolume_id\x18\x01 \x01(\tR\bvolumeId\x12\x1c\n" + "\trequester\x18\x02 \x01(\tR\trequester\x12\x1b\n" + - "\tread_only\x18\x03 \x01(\bR\breadOnly\"\x87\x01\n" + + "\tread_only\x18\x03 \x01(\bR\breadOnly\x12\x1a\n" + + "\bdetached\x18\x04 \x01(\bR\bdetached\"\xa3\x01\n" + "\x15VolumeMountStatusSpec\x12\x1b\n" + "\tvolume_id\x18\x01 \x01(\tR\bvolumeId\x12\x1c\n" + "\trequester\x18\x02 \x01(\tR\trequester\x12\x16\n" + "\x06target\x18\x03 \x01(\tR\x06target\x12\x1b\n" + - "\tread_only\x18\x04 \x01(\bR\breadOnly\"\x83\n" + + "\tread_only\x18\x04 \x01(\bR\breadOnly\x12\x1a\n" + + "\bdetached\x18\x05 \x01(\bR\bdetached\"\x83\n" + "\n" + "\x10VolumeStatusSpec\x12H\n" + "\x05phase\x18\x01 \x01(\x0e22.talos.resource.definitions.enums.BlockVolumePhaseR\x05phase\x12\x1a\n" + diff --git a/pkg/machinery/api/resource/definitions/block/block_vtproto.pb.go b/pkg/machinery/api/resource/definitions/block/block_vtproto.pb.go index 6711e9752..d0ae48749 100644 --- a/pkg/machinery/api/resource/definitions/block/block_vtproto.pb.go +++ b/pkg/machinery/api/resource/definitions/block/block_vtproto.pb.go @@ -901,6 +901,16 @@ func (m *MountRequestSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.Detached { + i-- + if m.Detached { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x30 + } if m.ReadOnly { i-- if m.ReadOnly { @@ -1065,6 +1075,16 @@ func (m *MountStatusSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.Detached { + i-- + if m.Detached { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x40 + } if m.EncryptionProvider != 0 { i = protohelpers.EncodeVarint(dAtA, i, uint64(m.EncryptionProvider)) i-- @@ -1725,6 +1745,16 @@ func (m *VolumeMountRequestSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.Detached { + i-- + if m.Detached { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x20 + } if m.ReadOnly { i-- if m.ReadOnly { @@ -1782,6 +1812,16 @@ func (m *VolumeMountStatusSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.Detached { + i-- + if m.Detached { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x28 + } if m.ReadOnly { i-- if m.ReadOnly { @@ -2496,6 +2536,9 @@ func (m *MountRequestSpec) SizeVT() (n int) { if m.ReadOnly { n += 2 } + if m.Detached { + n += 2 + } n += len(m.unknownFields) return n } @@ -2567,6 +2610,9 @@ func (m *MountStatusSpec) SizeVT() (n int) { if m.EncryptionProvider != 0 { n += 1 + protohelpers.SizeOfVarint(uint64(m.EncryptionProvider)) } + if m.Detached { + n += 2 + } n += len(m.unknownFields) return n } @@ -2803,6 +2849,9 @@ func (m *VolumeMountRequestSpec) SizeVT() (n int) { if m.ReadOnly { n += 2 } + if m.Detached { + n += 2 + } n += len(m.unknownFields) return n } @@ -2828,6 +2877,9 @@ func (m *VolumeMountStatusSpec) SizeVT() (n int) { if m.ReadOnly { n += 2 } + if m.Detached { + n += 2 + } n += len(m.unknownFields) return n } @@ -5535,6 +5587,26 @@ func (m *MountRequestSpec) UnmarshalVT(dAtA []byte) error { } } m.ReadOnly = bool(v != 0) + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Detached", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Detached = bool(v != 0) default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -6008,6 +6080,26 @@ func (m *MountStatusSpec) UnmarshalVT(dAtA []byte) error { break } } + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Detached", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Detached = bool(v != 0) default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -7607,6 +7699,26 @@ func (m *VolumeMountRequestSpec) UnmarshalVT(dAtA []byte) error { } } m.ReadOnly = bool(v != 0) + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Detached", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Detached = bool(v != 0) default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -7774,6 +7886,26 @@ func (m *VolumeMountStatusSpec) UnmarshalVT(dAtA []byte) error { } } m.ReadOnly = bool(v != 0) + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Detached", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Detached = bool(v != 0) default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/pkg/machinery/client/config/config.go b/pkg/machinery/client/config/config.go index 2f963d9e9..73778f6b3 100644 --- a/pkg/machinery/client/config/config.go +++ b/pkg/machinery/client/config/config.go @@ -7,8 +7,10 @@ package config import ( "bytes" "encoding/base64" + "errors" "fmt" "io" + "io/fs" "os" "path/filepath" @@ -248,7 +250,7 @@ func (c *Config) Merge(cfg *Config) []Rename { } func ensure(path string) error { - if _, err := os.Stat(path); os.IsNotExist(err) { + if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { config := &Config{ Context: "", Contexts: map[string]*Context{}, diff --git a/pkg/machinery/config/bundle/bundle.go b/pkg/machinery/config/bundle/bundle.go index 6d7478102..f0c9df832 100644 --- a/pkg/machinery/config/bundle/bundle.go +++ b/pkg/machinery/config/bundle/bundle.go @@ -8,6 +8,7 @@ package bundle import ( "errors" "fmt" + "io/fs" "os" "path/filepath" "strings" @@ -53,7 +54,7 @@ func NewBundle(opts ...Option) (*Bundle, error) { for _, configType := range []machine.Type{machine.TypeInit, machine.TypeControlPlane, machine.TypeWorker} { data, err := os.ReadFile(filepath.Join(options.ExistingConfigs, strings.ToLower(configType.String())+".yaml")) if err != nil { - if configType == machine.TypeInit && os.IsNotExist(err) { + if configType == machine.TypeInit && errors.Is(err, fs.ErrNotExist) { continue } @@ -85,7 +86,7 @@ func NewBundle(opts ...Option) (*Bundle, error) { // Pull existing talosconfig talosConfig, err := os.Open(filepath.Join(options.ExistingConfigs, "talosconfig")) - if os.IsNotExist(err) { // talosconfig is optional + if errors.Is(err, fs.ErrNotExist) { // talosconfig is optional return bundle, nil } diff --git a/pkg/machinery/config/configloader/configloader.go b/pkg/machinery/config/configloader/configloader.go index d658a6866..e202d532d 100644 --- a/pkg/machinery/config/configloader/configloader.go +++ b/pkg/machinery/config/configloader/configloader.go @@ -59,6 +59,11 @@ func NewFromFile(filepath string) (config.Provider, error) { return newConfig(f) } +// NewFromReader will take a reader and attempt to parse a config file from it. +func NewFromReader(f io.Reader) (config.Provider, error) { + return newConfig(f) +} + // NewFromStdin initializes a config provider by reading from stdin. func NewFromStdin() (config.Provider, error) { return newConfig(os.Stdin) diff --git a/pkg/machinery/config/encoder/markdown.go b/pkg/machinery/config/encoder/markdown.go index 0318afbcd..da44bdbad 100644 --- a/pkg/machinery/config/encoder/markdown.go +++ b/pkg/machinery/config/encoder/markdown.go @@ -9,6 +9,7 @@ import ( _ "embed" "errors" "fmt" + "io/fs" "os" "path/filepath" "slices" @@ -67,7 +68,7 @@ func (fd *FileDoc) Encode(root *Doc, frontmatter func(title, description string) // //nolint:gocyclo func (fd *FileDoc) Write(path string, frontmatter func(title, description string) string) error { - if stat, err := os.Stat(path); !os.IsNotExist(err) { + if stat, err := os.Stat(path); !errors.Is(err, fs.ErrNotExist) { if !stat.IsDir() { return errors.New("destination path should be a directory") } diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_stability_test.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_stability_test.go index b6a7a102f..2190925fd 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_stability_test.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_stability_test.go @@ -5,7 +5,9 @@ package v1alpha1_test import ( + "errors" "fmt" + "io/fs" "os" "testing" @@ -120,7 +122,7 @@ func testConfigStability(t *testing.T, in *generate.Input, versionContract *conf expectedPath := fmt.Sprintf("testdata/stability/%s/%s-%s.yaml", versionContract, flavor, machineType) expectedBytes, err := os.ReadFile(expectedPath) - if os.IsNotExist(err) && generateMode { + if errors.Is(err, fs.ErrNotExist) && generateMode { require.NoError(t, os.WriteFile(expectedPath, cfgBytes, 0o644)) t.Logf("generated %s", expectedPath) diff --git a/pkg/machinery/nethelpers/device.go b/pkg/machinery/nethelpers/device.go index 04fa34bc8..d37dc6b30 100644 --- a/pkg/machinery/nethelpers/device.go +++ b/pkg/machinery/nethelpers/device.go @@ -6,7 +6,9 @@ package nethelpers import ( "bytes" + "errors" "io" + "io/fs" "os" "path/filepath" "strings" @@ -41,7 +43,7 @@ func GetDeviceInfo(deviceName string) (*DeviceInfo, error) { _, err := os.Stat(path) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return &DeviceInfo{}, nil } diff --git a/pkg/machinery/resources/block/mount_request.go b/pkg/machinery/resources/block/mount_request.go index 61f9d7e17..6885c13d5 100644 --- a/pkg/machinery/resources/block/mount_request.go +++ b/pkg/machinery/resources/block/mount_request.go @@ -27,6 +27,7 @@ type MountRequestSpec struct { ParentMountID string `yaml:"parentID" protobuf:"2"` ReadOnly bool `yaml:"readOnly" protobuf:"5"` + Detached bool `yaml:"detached" protobuf:"6"` Requesters []string `yaml:"requesters" protobuf:"3"` RequesterIDs []string `yaml:"requesterIDs" protobuf:"4"` diff --git a/pkg/machinery/resources/block/mount_status.go b/pkg/machinery/resources/block/mount_status.go index 6e29562c6..a7328a58d 100644 --- a/pkg/machinery/resources/block/mount_status.go +++ b/pkg/machinery/resources/block/mount_status.go @@ -31,6 +31,20 @@ type MountStatusSpec struct { ReadOnly bool `yaml:"readOnly" protobuf:"5"` ProjectQuotaSupport bool `yaml:"projectQuotaSupport" protobuf:"6"` + Detached bool `yaml:"detached" protobuf:"8"` + + root any +} + +// SetRoot sets the XFS root for the mount. +func (m *MountStatusSpec) SetRoot(root any) { + m.root = root +} + +// Root gets the XFS root for the mount. +// It's not guaranteed to be set (may be nil). +func (m *MountStatusSpec) Root() any { + return m.root } // NewMountStatus initializes a MountStatus resource. diff --git a/pkg/machinery/resources/block/volume_mount_request.go b/pkg/machinery/resources/block/volume_mount_request.go index 6a6946910..9388365dc 100644 --- a/pkg/machinery/resources/block/volume_mount_request.go +++ b/pkg/machinery/resources/block/volume_mount_request.go @@ -27,6 +27,8 @@ type VolumeMountRequestSpec struct { ReadOnly bool `yaml:"readOnly" protobuf:"3"` + Detached bool `yaml:"detached" protobuf:"4"` + Requester string `yaml:"requester" protobuf:"2"` } diff --git a/pkg/machinery/resources/block/volume_mount_status.go b/pkg/machinery/resources/block/volume_mount_status.go index 1dbffdf65..abe2c3db2 100644 --- a/pkg/machinery/resources/block/volume_mount_status.go +++ b/pkg/machinery/resources/block/volume_mount_status.go @@ -28,6 +28,20 @@ type VolumeMountStatusSpec struct { Target string `yaml:"target" protobuf:"3"` ReadOnly bool `yaml:"readOnly" protobuf:"4"` + Detached bool `yaml:"detached" protobuf:"5"` + + root any +} + +// SetRoot sets the XFS root for the mount. +func (m *VolumeMountStatusSpec) SetRoot(root any) { + m.root = root +} + +// Root gets the XFS root for the mount. +// It's not guaranteed to be set (may be nil). +func (m *VolumeMountStatusSpec) Root() any { + return m.root } // NewVolumeMountStatus initializes a VolumeMountStatus resource. diff --git a/pkg/provision/providers/qemu/pflash.go b/pkg/provision/providers/qemu/pflash.go index 1383fa9ee..f0f5b0ae9 100644 --- a/pkg/provision/providers/qemu/pflash.go +++ b/pkg/provision/providers/qemu/pflash.go @@ -5,8 +5,10 @@ package qemu import ( + "errors" "fmt" "io" + "io/fs" "os" "github.com/siderolabs/talos/pkg/provision/providers/vm" @@ -37,7 +39,7 @@ func (p *provisioner) createPFlashImages(state *vm.State, nodeName string, pflas src, err = os.Open(sourcePath) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { continue } diff --git a/pkg/provision/providers/qemu/preflight_linux.go b/pkg/provision/providers/qemu/preflight_linux.go index 6a137e981..0db769b33 100644 --- a/pkg/provision/providers/qemu/preflight_linux.go +++ b/pkg/provision/providers/qemu/preflight_linux.go @@ -6,7 +6,9 @@ package qemu import ( "context" + "errors" "fmt" + "io/fs" "os" "os/exec" "path/filepath" @@ -42,7 +44,7 @@ func (check *preflightCheckContext) cniDirectories(ctx context.Context) error { for _, cniDir := range cniDirs { st, err := os.Stat(cniDir) if err != nil { - if !os.IsNotExist(err) { + if !errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("error checking CNI directory %q: %w", cniDir, err) } diff --git a/pkg/provision/providers/vm/ipam.go b/pkg/provision/providers/vm/ipam.go index aa65817a8..ed89907d8 100644 --- a/pkg/provision/providers/vm/ipam.go +++ b/pkg/provision/providers/vm/ipam.go @@ -7,7 +7,9 @@ package vm import ( "bufio" "encoding/json" + "errors" "fmt" + "io/fs" "net/netip" "os" "path/filepath" @@ -56,7 +58,7 @@ func DumpIPAMRecord(statePath string, record IPAMRecord) error { func LoadIPAMRecords(statePath string) (IPAMDatabase, error) { f, err := os.Open(filepath.Join(statePath, dbFile)) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return nil, nil } diff --git a/pkg/provision/providers/vm/process.go b/pkg/provision/providers/vm/process.go index daddb879e..4d5dea6ec 100644 --- a/pkg/provision/providers/vm/process.go +++ b/pkg/provision/providers/vm/process.go @@ -5,7 +5,9 @@ package vm import ( + "errors" "fmt" + "io/fs" "os" "syscall" "time" @@ -17,7 +19,7 @@ import ( func StopProcessByPidfile(pidPath string) error { pidFile, err := os.Open(pidPath) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return nil } diff --git a/pkg/provision/providers/vm/reflect.go b/pkg/provision/providers/vm/reflect.go index 84b7d0e0a..60a05725b 100644 --- a/pkg/provision/providers/vm/reflect.go +++ b/pkg/provision/providers/vm/reflect.go @@ -6,7 +6,9 @@ package vm import ( "context" + "errors" "fmt" + "io/fs" "os" "path/filepath" @@ -21,7 +23,7 @@ func (p *Provisioner) Reflect(ctx context.Context, clusterName, stateDirectory s st, err := os.Stat(statePath) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return nil, fmt.Errorf("cluster %q not found: %w", clusterName, err) } diff --git a/pkg/provision/providers/vm/state.go b/pkg/provision/providers/vm/state.go index eced43918..86dc4cd72 100644 --- a/pkg/provision/providers/vm/state.go +++ b/pkg/provision/providers/vm/state.go @@ -7,6 +7,7 @@ package vm import ( "errors" "fmt" + "io/fs" "os" "path/filepath" @@ -44,7 +45,7 @@ func NewState(statePath, provisionerName, clusterName string) (*State, error) { ) } - if !os.IsNotExist(err) { + if !errors.Is(err, fs.ErrNotExist) { return nil, fmt.Errorf("error checking state directory: %w", err) } diff --git a/internal/pkg/xfs/fsopen/fsopen_linux.go b/pkg/xfs/fsopen/fsopen_linux.go similarity index 92% rename from internal/pkg/xfs/fsopen/fsopen_linux.go rename to pkg/xfs/fsopen/fsopen_linux.go index c46229c69..595d65478 100644 --- a/internal/pkg/xfs/fsopen/fsopen_linux.go +++ b/pkg/xfs/fsopen/fsopen_linux.go @@ -2,6 +2,8 @@ // 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/. +//go:build linux + // Package fsopen provides a simple interface to create and manage a filesystem // using the Linux syscalls for filesystem operations. package fsopen @@ -14,8 +16,8 @@ import ( "golang.org/x/sys/unix" - "github.com/siderolabs/talos/internal/pkg/xfs" "github.com/siderolabs/talos/pkg/makefs" + "github.com/siderolabs/talos/pkg/xfs" ) // ErrRepairUnsupported is reported when the filesystem does not support repairs. @@ -28,8 +30,6 @@ type FS struct { fstype string source string - printer func(string, ...any) - boolParams map[string]struct{} stringParams map[string][]string binaryParams map[string][][]byte @@ -84,19 +84,10 @@ func (fs *FS) Open() (int, error) { return fs.mntfd, err } -func discard(string, ...any) {} - //nolint:gocyclo func (fs *FS) new() (err error) { var fsfd int - printer := discard - if fs.printer != nil { - printer = fs.printer - } - - printer("creating filesystem of type: %q", fs.fstype) - fsfd, err = unix.Fsopen(fs.fstype, unix.FSOPEN_CLOEXEC) if err != nil { return fmt.Errorf("unix.Fsopen fstype=%q failed: %w", fs.fstype, err) @@ -111,16 +102,12 @@ func (fs *FS) new() (err error) { }() if fs.source != "" { - printer("setting source: %q", fs.source) - if err := unix.FsconfigSetString(fsfd, "source", fs.source); err != nil { return fmt.Errorf("FSCONFIG_SET_STRING failed: %w: key=%q value=%q", err, "source", fs.source) } } for key := range fs.boolParams { - printer("setting boolean flag: %q", key) - if err := unix.FsconfigSetFlag(fsfd, key); err != nil { return fmt.Errorf("FSCONFIG_SET_FLAG failed: %w: key=%q", err, key) } @@ -128,8 +115,6 @@ func (fs *FS) new() (err error) { for key, binary := range fs.binaryParams { for _, bf := range binary { - printer("setting binary param: %q", key) - if err := unix.FsconfigSetBinary(fsfd, key, bf); err != nil { return fmt.Errorf("FSCONFIG_SET_BINARY failed: %w: key=%q", err, key) } @@ -138,8 +123,6 @@ func (fs *FS) new() (err error) { for key, values := range fs.stringParams { for _, value := range values { - printer("setting string param: %q=%q", key, value) - if err := unix.FsconfigSetString(fsfd, key, value); err != nil { return fmt.Errorf("FSCONFIG_SET_BINARY failed: %w: key=%q", err, key) } diff --git a/internal/pkg/xfs/fsopen/options_linux.go b/pkg/xfs/fsopen/options_linux.go similarity index 91% rename from internal/pkg/xfs/fsopen/options_linux.go rename to pkg/xfs/fsopen/options_linux.go index d879f0de5..dd1f8e5ec 100644 --- a/internal/pkg/xfs/fsopen/options_linux.go +++ b/pkg/xfs/fsopen/options_linux.go @@ -2,6 +2,8 @@ // 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/. +//go:build linux + package fsopen // Option is a functional option for configuring a filesystem instance. @@ -27,15 +29,6 @@ func WithMountFlags(flag int) Option { } } -// WithPrinter adds a printer to the filesystem configuration. -func WithPrinter(printer func(string, ...any)) Option { - return Option{ - set: func(t *FS) { - t.printer = printer - }, - } -} - // WithProjectQuota sets the project quota flag. func WithProjectQuota(enabled bool) Option { return Option{ diff --git a/internal/pkg/xfs/fsopen_test.go b/pkg/xfs/fsopen_test.go similarity index 79% rename from internal/pkg/xfs/fsopen_test.go rename to pkg/xfs/fsopen_test.go index 76b191c08..eb0349e26 100644 --- a/internal/pkg/xfs/fsopen_test.go +++ b/pkg/xfs/fsopen_test.go @@ -2,7 +2,7 @@ // 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/. -//go:build unix +//go:build linux package xfs_test @@ -12,8 +12,8 @@ import ( "github.com/stretchr/testify/require" - "github.com/siderolabs/talos/internal/pkg/xfs" - "github.com/siderolabs/talos/internal/pkg/xfs/fsopen" + "github.com/siderolabs/talos/pkg/xfs" + "github.com/siderolabs/talos/pkg/xfs/fsopen" ) func TestFsopen(t *testing.T) { @@ -32,9 +32,7 @@ func TestFsopen(t *testing.T) { t.Run(tc.fstype, func(t *testing.T) { t.Parallel() - fs := fsopen.New(tc.fstype, tc.opts...) - - root := &xfs.UnixRoot{FS: fs} + root := &xfs.UnixRoot{FS: fsopen.New(tc.fstype, tc.opts...)} err := root.OpenFS() require.NoError(t, err) diff --git a/pkg/xfs/helpers_linux.go b/pkg/xfs/helpers_linux.go new file mode 100644 index 000000000..f470840cb --- /dev/null +++ b/pkg/xfs/helpers_linux.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/. + +//go:build linux + +package xfs + +import ( + "errors" + "fmt" + "io/fs" + "os" + "syscall" +) + +// AsOSFile attempts to convert fs.File to *os.File. +func AsOSFile(f fs.File, name string) (*os.File, error) { + ff, ok := f.(File) + if !ok { + return nil, errors.ErrUnsupported + } + + newFd, err := syscall.Dup(int(ff.Fd())) + if err != nil { + return nil, fmt.Errorf("failed to duplicate file descriptor: %w", err) + } + + return os.NewFile(uintptr(newFd), name), nil +} diff --git a/internal/pkg/xfs/opentree/opentree_linux.go b/pkg/xfs/opentree/opentree_linux.go similarity index 97% rename from internal/pkg/xfs/opentree/opentree_linux.go rename to pkg/xfs/opentree/opentree_linux.go index a8b5c9ddf..8f12978b3 100644 --- a/internal/pkg/xfs/opentree/opentree_linux.go +++ b/pkg/xfs/opentree/opentree_linux.go @@ -2,6 +2,8 @@ // 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/. +//go:build linux + // Package opentree provides a simple interface to create and manage a subfilesystem // using the `open_tree` syscall. It allows for creating a new subfilesystem // by cloning an existing filesystem tree and provides a method to close the filesystem @@ -13,7 +15,7 @@ import ( "golang.org/x/sys/unix" - xfs "github.com/siderolabs/talos/internal/pkg/xfs" + xfs "github.com/siderolabs/talos/pkg/xfs" ) // FS represents a subfilesystem that can be created and managed. diff --git a/internal/pkg/xfs/opentree_test.go b/pkg/xfs/opentree_test.go similarity index 83% rename from internal/pkg/xfs/opentree_test.go rename to pkg/xfs/opentree_test.go index 74246967e..c50a9ff47 100644 --- a/internal/pkg/xfs/opentree_test.go +++ b/pkg/xfs/opentree_test.go @@ -2,7 +2,7 @@ // 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/. -//go:build unix +//go:build linux package xfs_test @@ -13,9 +13,9 @@ import ( "github.com/stretchr/testify/require" "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" - "github.com/siderolabs/talos/internal/pkg/xfs" - "github.com/siderolabs/talos/internal/pkg/xfs/fsopen" - "github.com/siderolabs/talos/internal/pkg/xfs/opentree" + "github.com/siderolabs/talos/pkg/xfs" + "github.com/siderolabs/talos/pkg/xfs/fsopen" + "github.com/siderolabs/talos/pkg/xfs/opentree" ) func TestOpentree(t *testing.T) { @@ -30,9 +30,7 @@ func TestOpentree(t *testing.T) { testRoot := t.TempDir() - fs := opentree.NewFromPath(testRoot) - - root := &xfs.UnixRoot{FS: fs} + root := &xfs.UnixRoot{FS: opentree.NewFromPath(testRoot)} err := root.OpenFS() require.NoError(t, err) @@ -86,9 +84,7 @@ func TestOpentree(t *testing.T) { t.Skip("OpenTree on Anonymous FS requires kernel 6.15.0+") } - fs := fsopen.New("tmpfs") - - roRoot := &xfs.UnixRoot{FS: fs} + roRoot := &xfs.UnixRoot{FS: fsopen.New("tmpfs")} err = roRoot.OpenFS() require.NoError(t, err) @@ -107,10 +103,6 @@ func TestOpentree(t *testing.T) { err = rwRoot.OpenFS() require.NoError(t, err) - t.Cleanup(func() { - require.NoError(t, fs.Close()) - }) - testFilesystem(t, rwRoot, roRoot) }) } diff --git a/internal/pkg/xfs/osroot.go b/pkg/xfs/osroot.go similarity index 88% rename from internal/pkg/xfs/osroot.go rename to pkg/xfs/osroot.go index 692868654..2402eacf6 100644 --- a/internal/pkg/xfs/osroot.go +++ b/pkg/xfs/osroot.go @@ -60,6 +60,11 @@ func (root *OSRoot) Remove(name string) error { return os.Remove(filepath.Join(root.Shadow, name)) } +// Rename renames a file or directory in the root filesystem from old to new. +func (root *OSRoot) Rename(oldname, newname string) error { + return os.Rename(filepath.Join(root.Shadow, oldname), filepath.Join(root.Shadow, newname)) +} + // Source returns the source of the underlying filesystem. func (root *OSRoot) Source() string { return root.Shadow diff --git a/internal/pkg/xfs/osroot_test.go b/pkg/xfs/osroot_test.go similarity index 92% rename from internal/pkg/xfs/osroot_test.go rename to pkg/xfs/osroot_test.go index f32c05186..635c1f443 100644 --- a/internal/pkg/xfs/osroot_test.go +++ b/pkg/xfs/osroot_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/siderolabs/talos/internal/pkg/xfs" + "github.com/siderolabs/talos/pkg/xfs" ) func TestOs(t *testing.T) { diff --git a/internal/pkg/xfs/root.go b/pkg/xfs/root.go similarity index 79% rename from internal/pkg/xfs/root.go rename to pkg/xfs/root.go index 0ab89bf82..b4a684d17 100644 --- a/internal/pkg/xfs/root.go +++ b/pkg/xfs/root.go @@ -18,7 +18,6 @@ import ( "path/filepath" "strconv" "strings" - "syscall" ) // FS is an interface for creating file system handles. @@ -32,6 +31,8 @@ type FS interface { } // Root is an interface that extends the standard fs.FS interface with Write capabilities. +// +//nolint:interfacebloat type Root interface { fs.FS @@ -43,6 +44,7 @@ type Root interface { Mkdir(name string, perm os.FileMode) error OpenFile(name string, flags int, perm os.FileMode) (File, error) Remove(name string) error + Rename(oldname, newname string) error Source() string FSType() string @@ -65,22 +67,22 @@ var _ File = (*os.File)(nil) // ReadFile wraps fs.ReadFile to read a file from the specified FileSystem. func ReadFile(root Root, name string) ([]byte, error) { - return fs.ReadFile(root, strings.TrimLeft(name, "/")) + return fs.ReadFile(root, name) } // ReadDir wraps fs.ReadDir to read a directory from the specified FileSystem. func ReadDir(root Root, name string) ([]fs.DirEntry, error) { - return fs.ReadDir(root, strings.TrimLeft(name, "/")) + return fs.ReadDir(root, name) } // Stat wraps fs.Stat to get the file or directory information from the specified FileSystem. func Stat(root Root, name string) (fs.FileInfo, error) { - return fs.Stat(root, strings.TrimLeft(name, "/")) + return fs.Stat(root, name) } // WriteFile is equivalent of os.WriteFile acting on specified FileSystem. func WriteFile(root Root, name string, data []byte, perm os.FileMode) error { - f, err := root.OpenFile(strings.TrimLeft(name, "/"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) + f, err := root.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) if err != nil { return err } @@ -93,39 +95,24 @@ func WriteFile(root Root, name string, data []byte, perm os.FileMode) error { return err } -// AsOSFile attempts to convert fs.File to *os.File. -func AsOSFile(f fs.File, name string) (*os.File, error) { - ff, ok := f.(File) - if !ok { - return nil, errors.ErrUnsupported - } - - newFd, err := syscall.Dup(int(ff.Fd())) - if err != nil { - return nil, fmt.Errorf("failed to duplicate file descriptor: %w", err) - } - - return os.NewFile(uintptr(newFd), name), nil -} - // Open wraps (FS).Open. func Open(root Root, name string) (fs.File, error) { - return root.Open(strings.TrimLeft(name, "/")) + return root.Open(name) } // OpenFile wraps (FS).OpenFile. func OpenFile(root Root, name string, flags int, perm os.FileMode) (File, error) { - return root.OpenFile(strings.TrimLeft(name, "/"), flags, perm) + return root.OpenFile(name, flags, perm) } // Mkdir wraps (FS).Mkdir. func Mkdir(root Root, name string, perm os.FileMode) error { - return root.Mkdir(strings.TrimLeft(name, "/"), perm) + return root.Mkdir(name, perm) } // MkdirAll is equivalent of os.MkdirAll acting on specified FileSystem. func MkdirAll(root Root, name string, perm os.FileMode) error { - components := SplitPath(strings.TrimLeft(name, "/")) + components := SplitPath(name) for i := range len(components) + 1 { dir := filepath.Join(components[:i]...) @@ -149,8 +136,6 @@ func MkdirTemp(root Root, dir, pattern string) (string, error) { dir = os.TempDir() } - dir = strings.TrimLeft(dir, "/") - if pattern == "" { pattern = "tmp" } @@ -188,14 +173,14 @@ func MkdirTemp(root Root, dir, pattern string) (string, error) { // Remove wraps (FS).Remove. func Remove(root Root, name string) error { - return root.Remove(strings.TrimLeft(name, "/")) + return root.Remove(name) } // RemoveAll is equivalent of os.RemoveAll acting on specified FileSystem. func RemoveAll(root Root, name string) (err error) { var f fs.File - f, err = root.Open(strings.TrimLeft(name, "/")) + f, err = root.Open(name) if err != nil { if errors.Is(err, os.ErrNotExist) { return nil @@ -214,22 +199,27 @@ func RemoveAll(root Root, name string) (err error) { } if !stat.IsDir() { - return root.Remove(strings.TrimLeft(name, "/")) + return root.Remove(name) } - entries, err := ReadDir(root, strings.TrimLeft(name, "/")) + entries, err := ReadDir(root, name) if err != nil { return err } for _, entry := range entries { - childPath := filepath.Join(strings.TrimLeft(name, "/"), entry.Name()) + childPath := filepath.Join(name, entry.Name()) if err := RemoveAll(root, childPath); err != nil { return err } } - return root.Remove(strings.TrimLeft(name, "/")) + return root.Remove(name) +} + +// Rename wraps (FS).Rename. +func Rename(root Root, oldname, newname string) error { + return root.Rename(oldname, newname) } // SplitPath splits a path into its components, similar to filepath.Split but returns all parts. diff --git a/internal/pkg/xfs/root_unix.go b/pkg/xfs/root_linux.go similarity index 77% rename from internal/pkg/xfs/root_unix.go rename to pkg/xfs/root_linux.go index 4d3c187a2..f18a360c0 100644 --- a/internal/pkg/xfs/root_unix.go +++ b/pkg/xfs/root_linux.go @@ -2,12 +2,15 @@ // 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/. +//go:build linux + package xfs import ( "fmt" "io/fs" "os" + "strings" "golang.org/x/sys/unix" ) @@ -70,29 +73,29 @@ func (root *UnixRoot) Fd() (int, error) { // Mkdir creates a new directory in the root filesystem with the specified name and permissions. func (root *UnixRoot) Mkdir(name string, perm os.FileMode) error { - return unix.Mkdirat(root.mntfd, name, uint32(perm)) + return unix.Mkdirat(root.mntfd, strings.TrimLeft(name, "/"), uint32(perm)) } // Open opens a file in the root filesystem with the specified name in read-only mode. func (root *UnixRoot) Open(name string) (fs.File, error) { - return root.OpenFile(name, unix.O_RDONLY, 0) + return root.OpenFile(strings.TrimLeft(name, "/"), unix.O_RDONLY, 0) } // OpenFile opens a file in the root filesystem with the specified name, flags, and permissions. func (root *UnixRoot) OpenFile(name string, flags int, perm os.FileMode) (File, error) { - fd, err := unix.Openat(root.mntfd, name, flags, uint32(perm)) + fd, err := unix.Openat(root.mntfd, strings.TrimLeft(name, "/"), flags, uint32(perm)) if err != nil { return nil, err } - return os.NewFile(uintptr(fd), name), nil + return os.NewFile(uintptr(fd), strings.TrimLeft(name, "/")), nil } // Remove removes a file or directory from the root filesystem. func (root *UnixRoot) Remove(name string) error { flags := 0 - info, err := root.stat(name) + info, err := root.stat(strings.TrimLeft(name, "/")) if err != nil { return err } @@ -101,11 +104,16 @@ func (root *UnixRoot) Remove(name string) error { flags = unix.AT_REMOVEDIR } - return unix.Unlinkat(root.mntfd, name, flags) + return unix.Unlinkat(root.mntfd, strings.TrimLeft(name, "/"), flags) +} + +// Rename renames a file or directory in the root filesystem from old to new. +func (root *UnixRoot) Rename(oldname, newname string) error { + return unix.Renameat(root.mntfd, strings.TrimLeft(oldname, "/"), root.mntfd, strings.TrimLeft(newname, "/")) } func (root *UnixRoot) stat(name string) (os.FileInfo, error) { - f, err := root.Open(name) + f, err := root.Open(strings.TrimLeft(name, "/")) if err != nil { return nil, err } diff --git a/internal/pkg/xfs/root_test.go b/pkg/xfs/root_test.go similarity index 79% rename from internal/pkg/xfs/root_test.go rename to pkg/xfs/root_test.go index b2e9751ec..5efe28f94 100644 --- a/internal/pkg/xfs/root_test.go +++ b/pkg/xfs/root_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/siderolabs/talos/internal/pkg/xfs" + "github.com/siderolabs/talos/pkg/xfs" ) const testDir = "testdir" @@ -184,6 +184,50 @@ func testFilesystem(t *testing.T, rwRoot xfs.Root, roRoot xfs.Root) { require.NoError(t, err, "stat file %q failed", name) assert.False(t, actual.IsDir()) }) + + t.Run("Rename", func(t *testing.T) { + t.Parallel() + + t.Run("Dir", func(t *testing.T) { + t.Parallel() + + oldName := filepath.Join(testDir, "rename.old.d", "test") + newName := filepath.Join(testDir, "rename.new.d", "test") + + touchTree(t, rwRoot, oldName) + + err := xfs.Rename(rwRoot, filepath.Dir(oldName), filepath.Dir(newName)) + assert.NoError(t, err) + + newDirStat, err := xfs.Stat(roRoot, filepath.Dir(newName)) + require.NoError(t, err, "stat dir %q failed", filepath.Dir(newName)) + assert.True(t, newDirStat.IsDir()) + + _, err = xfs.Stat(roRoot, newName) + require.NoError(t, err, "stat file %q failed", newName) + + _, err = xfs.Stat(roRoot, oldName) + require.ErrorIs(t, err, os.ErrNotExist, "stat dir %q failed", filepath.Dir(oldName)) + }) + + t.Run("File", func(t *testing.T) { + t.Parallel() + + oldName := filepath.Join(testDir, "rename.old.test") + newName := filepath.Join(testDir, "rename.new.test") + + touchTree(t, rwRoot, oldName) + + err := xfs.Rename(rwRoot, oldName, newName) + assert.NoError(t, err) + + _, err = xfs.Stat(roRoot, newName) + require.NoError(t, err, "stat file %q failed", newName) + + _, err = xfs.Stat(roRoot, oldName) + require.ErrorIs(t, err, os.ErrNotExist, "stat file %q failed", oldName) + }) + }) } func writeFile(tb testing.TB, root xfs.Root, name string, content []byte) { diff --git a/tools/structprotogen/main.go b/tools/structprotogen/main.go index 9873c6660..5a4d88709 100644 --- a/tools/structprotogen/main.go +++ b/tools/structprotogen/main.go @@ -7,7 +7,9 @@ package main //nolint:gci import ( + "errors" "fmt" + "io/fs" "os" "path" "path/filepath" @@ -148,7 +150,7 @@ func withFile(filename string, fn func(f *os.File) error) error { dir := filepath.Dir(filename) _, err := os.Stat(dir) - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { err = os.MkdirAll(dir, 0o755) if err != nil { return err diff --git a/tools/structprotogen/types/types.go b/tools/structprotogen/types/types.go index 67d0ab812..ad97b04cb 100644 --- a/tools/structprotogen/types/types.go +++ b/tools/structprotogen/types/types.go @@ -136,6 +136,10 @@ func ParseDeclsData(sortedPkgs slices.Sorted[*PkgDecl], taggedStructs ast.Tagged for j := 0; j < structType.NumFields(); j++ { field := structType.Field(j) + if !field.Exported() { + continue + } + v := sliceutil.GetOrAdd(&result, &Type{ Pkg: pkg.path, Name: structName, @@ -184,6 +188,10 @@ func FindExternalTypes(pkgsTypes slices.Sorted[*Type], taggedStructs ast.TaggedS for j := 0; j < typ.fields.Len(); j++ { field := typ.fields.Get(j) + if !field.TypeData.Exported() { + continue + } + typeData := TypeInfo(field.TypeData.Type()) if typePkg := typeData.typePkg(); typePkg != "" { diff --git a/website/content/v1.12/reference/api.md b/website/content/v1.12/reference/api.md index 6c5937520..99a291b2a 100644 --- a/website/content/v1.12/reference/api.md +++ b/website/content/v1.12/reference/api.md @@ -5426,6 +5426,7 @@ MountRequestSpec is the spec for MountRequest. | requesters | [string](#string) | repeated | | | requester_i_ds | [string](#string) | repeated | | | read_only | [bool](#bool) | | | +| detached | [bool](#bool) | | | @@ -5469,6 +5470,7 @@ MountStatusSpec is the spec for MountStatus. | read_only | [bool](#bool) | | | | project_quota_support | [bool](#bool) | | | | encryption_provider | [talos.resource.definitions.enums.BlockEncryptionProviderType](#talos.resource.definitions.enums.BlockEncryptionProviderType) | | | +| detached | [bool](#bool) | | | @@ -5644,6 +5646,7 @@ VolumeMountRequestSpec is the spec for VolumeMountRequest. | volume_id | [string](#string) | | | | requester | [string](#string) | | | | read_only | [bool](#bool) | | | +| detached | [bool](#bool) | | | @@ -5662,6 +5665,7 @@ VolumeMountStatusSpec is the spec for VolumeMountStatus. | requester | [string](#string) | | | | target | [string](#string) | | | | read_only | [bool](#bool) | | | +| detached | [bool](#bool) | | |