From ff3a7e48251be9223ae546f97fba9c1c5b4f2339 Mon Sep 17 00:00:00 2001 From: Mathieu Grzybek Date: Thu, 8 Jan 2026 16:40:48 +0100 Subject: [PATCH] cexec: grab the mountpoints using the metadata service if requested. Signed-off-by: Mathieu Grzybek --- cexec/README.md | 22 +++++++++- cexec/main.go | 112 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 103 insertions(+), 31 deletions(-) diff --git a/cexec/README.md b/cexec/README.md index 9892597..09f50b8 100644 --- a/cexec/README.md +++ b/cexec/README.md @@ -40,6 +40,22 @@ actions: DEBIAN_FRONTEND: noninteractive ``` +If you want to use the mountpoints provided by metadata, replace the env variables called `BLOCK_DEVICE` and `FS_TYPE` +by `MIRROR_HOST` and `METADATA_SERVICE_PORT` (if required). + +```yaml +actions: +- name: Use cexec to check the mount points using the metedata + image: registry.ring0:5000/tinkerbell/actions/cexec:dev + timeout: 3601 + environment: + MIRROR_HOST: 192.168.3.5 + METADATA_SERVICE_PORT: 7172 + CHROOT: y + DEFAULT_INTERPRETER: "/bin/bash -c" + CMD_LINE: "df -h" +``` + ### Environment variables and CLI flags All options can be set either via environment variables or CLI flags. @@ -47,13 +63,15 @@ CLI flags take precedence over environment variables, which take precedence over | Env variable | Flag | Type | Default Value | Required | Description | |--------------|------|------|---------------|----------|-------------| -| `BLOCK_DEVICE` | `--block-device` | string | "" | yes | The block device to mount. | -| `FS_TYPE` | `--fs-type` | string | "" | yes | The filesystem type of the block device. | +| `BLOCK_DEVICE` | `--block-device` | string | "" | no | The block device to mount. | +| `FS_TYPE` | `--fs-type` | string | "" | no | The filesystem type of the block device. | | `CHROOT` | `--chroot` | string | "" | no | If set to `y` (or a non empty string), the Action will execute the given command within a chroot environment. This option is DEPRECATED. Future versions will always chroot. | | `CMD_LINE` | `--cmd-line` | string | "" | yes | The command to execute. | | `DEFAULT_INTERPRETER` | `--default-interpreter` | string | "" | no | The default interpreter to use when executing commands. This is useful when you need to execute multiple commands. | | `UPDATE_RESOLV_CONF` | `--update-resolv-conf` | boolean | false | no | If set to `true`, the cexec Action will update the `/etc/resolv.conf` file within the chroot environment with the `/etc/resolv.conf` from the host. | | `JSON_OUTPUT` | `--json-output` | boolean | true | no | If set to `true`, the cexec Action will log output in JSON format. The defaults to `true`. If set to `false`, the cexec Action will log output in plain text format. | +| `MIRROR_HOST` | `--mirror-host` | string | "" | no | The hostname of the metadata service. If defined, `.hardware.spec.metadata.storage.filesystems` will be used instead of `BLOCK_DEVICE` | +| `METADATA_SERVICE_PORT` | `--metadata-service-port | int | 7172 | no | The port of the metadata service. | Any environment variables you set on the Action will be available to the command you execute. For example, if you set `DEBIAN_FRONTEND: noninteractive` as an environment variable, it will be available to the command you execute. diff --git a/cexec/main.go b/cexec/main.go index 6d957d5..4862a66 100644 --- a/cexec/main.go +++ b/cexec/main.go @@ -14,6 +14,8 @@ import ( "syscall" "github.com/peterbourgon/ff/v3" + + "github.com/tinkerbell/actions/rootio/storage" ) const ( @@ -22,27 +24,35 @@ const ( ) type settings struct { - blockDevice string - filesystemType string - chroot string - defaultInterpreter string - cmdLine string - updateResolvConf bool + blockDevice string + filesystemType string + chroot string + defaultInterpreter string + cmdLine string + updateResolvConf bool + mirrorHost string + metadataServicePort int } +var metadata *storage.Metadata + func main() { + var err error + ctx, done := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGHUP, syscall.SIGTERM) defer done() logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) fs := flag.NewFlagSet("cexec", flag.ExitOnError) s := settings{} - fs.StringVar(&s.blockDevice, "block-device", "", "block device to mount (required)") - fs.StringVar(&s.filesystemType, "fs-type", "", "filesystem type (required)") + fs.StringVar(&s.blockDevice, "block-device", "", "block device to mount") + fs.StringVar(&s.filesystemType, "fs-type", "", "filesystem type") fs.StringVar(&s.chroot, "chroot", "", "use chroot environment to run given command (deprecated)") fs.StringVar(&s.defaultInterpreter, "default-interpreter", "", "default interpreter (optional)") fs.StringVar(&s.cmdLine, "cmd-line", "", "command line to execute (required)") fs.BoolVar(&s.updateResolvConf, "update-resolv-conf", false, "update /etc/resolv.conf in chroot environment (optional)") + fs.StringVar(&s.mirrorHost, "mirror-host", "", "Metadata host (optional)") + fs.IntVar(&s.metadataServicePort, "metadata-service-port", 7172, "Metadata service port (optional)") jsonLogger := fs.Bool("json-outout", true, "enable json output for logging") if err := ff.Parse(fs, os.Args[1:], ff.WithEnvVarNoPrefix()); err != nil { @@ -58,6 +68,15 @@ func main() { os.Exit(20) } + if os.Getenv("MIRROR_HOST") != "" { + metadata, err = storage.RetrieveData() + if err != nil { + logger.Error(err.Error()) + os.Exit(20) + } + fmt.Printf("Successfully parsed the MetaData, Found [%d] Disks\n", len(metadata.Instance.Storage.Disks)) + } + // TODO(jacobweinstock): add field validations for the settings struct. if *jsonLogger { @@ -72,6 +91,8 @@ func main() { "cmd-line", s.cmdLine, "json-output", *jsonLogger, "update-resolv-conf", s.updateResolvConf, + "mirror-host", s.mirrorHost, + "metadata-service-port", s.metadataServicePort, ) if err := s.cexec(ctx, logger); err != nil { @@ -82,11 +103,13 @@ func main() { func (s settings) checkRequiredFields() []string { var missingFields []string - if s.blockDevice == "" { - missingFields = append(missingFields, "block-device") - } - if s.filesystemType == "" { - missingFields = append(missingFields, "fs-type") + if s.mirrorHost == "" { + if s.blockDevice == "" { + missingFields = append(missingFields, "block-device") + } + if s.filesystemType == "" { + missingFields = append(missingFields, "fs-type") + } } if s.cmdLine == "" { missingFields = append(missingFields, "cmd-line") @@ -95,29 +118,60 @@ func (s settings) checkRequiredFields() []string { } func (s settings) cexec(ctx context.Context, log *slog.Logger) error { + var err error log.Info("CEXEC - Chroot Exec") - if s.blockDevice == "" { - return errors.New("no Block Device speified with Environment Variable [BLOCK_DEVICE]") - } - // Create the /mountAction mountpoint (no folders exist previously in scratch container) - if err := os.Mkdir(mountAction, os.ModeDir); err != nil { + if err = os.Mkdir(mountAction, os.ModeDir); err != nil { return fmt.Errorf("error creating the mount point [%s], error: %w", mountAction, err) } - // Mount the block device to the /mountAction point - if err := syscall.Mount(s.blockDevice, mountAction, s.filesystemType, 0, ""); err != nil { - return fmt.Errorf("error mounting [%s] -> [%s], error: %v", s.blockDevice, mountAction, err) - } - defer func() { - if err := syscall.Unmount(mountAction, 0); err != nil { - log.Error("error unmounting device", "source", s.blockDevice, "destination", mountAction, "error", err) - } else { - log.Info("unmounted device successfully", "source", s.blockDevice, "destination", mountAction) + if metadata != nil && len(metadata.Instance.Storage.Filesystems) > 0 { + // Mount the block device according to the metadata to the /mountAction point + log.Info("Using the Block Devices from metadata") + + for _, fileSystem := range metadata.Instance.Storage.Filesystems { + if fileSystem.Mount.Format != "SWAP" { + fileSystem.Mount.Point = fmt.Sprintf("%s/%s", mountAction, fileSystem.Mount.Point) + err = os.MkdirAll(fileSystem.Mount.Point, 0755) + if err != nil { + log.Error("error creating directory mountpoint", "directory", fileSystem.Mount.Point) + } else { + err := storage.Mount(fileSystem) + if err != nil { + log.Error(err.Error()) + } else { + defer func() { + if err := syscall.Unmount(fileSystem.Mount.Point, 0); err != nil { + log.Error("error unmounting device", "source", fileSystem.Mount.Device, "destination", fileSystem.Mount.Point, "error", err) + } else { + log.Info("unmounted device successfully", "source", fileSystem.Mount.Device, "destination", fileSystem.Mount.Point) + } + }() + } + } + } + } + } else { + // Mount the block device from env to the /mountAction point + log.Info("Using the Block Device from env") + + if s.blockDevice == "" { + return errors.New("no Block Device specified with Environment Variable [BLOCK_DEVICE]") + } + + if err := syscall.Mount(s.blockDevice, mountAction, s.filesystemType, 0, ""); err != nil { + return fmt.Errorf("error mounting [%s] -> [%s], error: %v", s.blockDevice, mountAction, err) } - }() - log.Info("mounted device successfully", "source", s.blockDevice, "destination", mountAction) + defer func() { + if err := syscall.Unmount(mountAction, 0); err != nil { + log.Error("error unmounting device", "source", s.blockDevice, "destination", mountAction, "error", err) + } else { + log.Info("unmounted device successfully", "source", s.blockDevice, "destination", mountAction) + } + }() + log.Info("mounted device successfully", "source", s.blockDevice, "destination", mountAction) + } if s.chroot != "" { if s.updateResolvConf {