Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions cexec/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,38 @@ 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.
CLI flags take precedence over environment variables, which take precedence over default values.

| 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.
Expand Down
112 changes: 83 additions & 29 deletions cexec/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"syscall"

"github.com/peterbourgon/ff/v3"

"github.com/tinkerbell/actions/rootio/storage"
)

const (
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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")
Expand All @@ -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 {
Expand Down