diff --git a/internal/distro/distro.go b/internal/distro/distro.go index ef4a3e9bd..e6865fc13 100644 --- a/internal/distro/distro.go +++ b/internal/distro/distro.go @@ -27,8 +27,9 @@ var ( diskByLabelDir = "/dev/disk/by-label" diskByPartUUIDDir = "/dev/disk/by-partuuid" - // File paths + // initrd file paths kernelCmdlinePath = "/proc/cmdline" + bootIDPath = "/proc/sys/kernel/random/boot_id" // initramfs directory containing distro-provided base config systemConfigDir = "/usr/lib/ignition" @@ -74,8 +75,9 @@ var ( // ".ssh/authorized_keys" ("false"). writeAuthorizedKeysFragment = "true" - luksInitramfsKeyFilePath = "/run/ignition/luks-keyfiles/" - luksRealRootKeyFilePath = "/etc/luks/" + // Special file paths in the real root + luksRealRootKeyFilePath = "/etc/luks/" + resultFilePath = "/var/lib/ignition/result.json" ) func DiskByIDDir() string { return diskByIDDir } @@ -83,6 +85,7 @@ func DiskByLabelDir() string { return diskByLabelDir } func DiskByPartUUIDDir() string { return diskByPartUUIDDir } func KernelCmdlinePath() string { return kernelCmdlinePath } +func BootIDPath() string { return bootIDPath } func SystemConfigDir() string { return fromEnv("SYSTEM_CONFIG_DIR", systemConfigDir) } func GroupaddCmd() string { return groupaddCmd } @@ -113,8 +116,8 @@ func CryptsetupCmd() string { return cryptsetupCmd } func KargsCmd() string { return kargsCmd } -func LuksInitramfsKeyFilePath() string { return luksInitramfsKeyFilePath } -func LuksRealRootKeyFilePath() string { return luksRealRootKeyFilePath } +func LuksRealRootKeyFilePath() string { return luksRealRootKeyFilePath } +func ResultFilePath() string { return resultFilePath } func SelinuxRelabel() bool { return bakedStringToBool(selinuxRelabel) && !BlackboxTesting() } func BlackboxTesting() bool { return bakedStringToBool(blackboxTesting) } diff --git a/internal/exec/engine.go b/internal/exec/engine.go index 9d245274e..04e22f03b 100644 --- a/internal/exec/engine.go +++ b/internal/exec/engine.go @@ -40,6 +40,7 @@ import ( "github.com/coreos/ignition/v2/internal/providers/cmdline" "github.com/coreos/ignition/v2/internal/providers/system" "github.com/coreos/ignition/v2/internal/resource" + "github.com/coreos/ignition/v2/internal/state" "github.com/coreos/ignition/v2/internal/util" "github.com/coreos/vcontext/report" @@ -69,13 +70,7 @@ type Engine struct { Root string PlatformConfig platform.Config Fetcher *resource.Fetcher - fetchedConfigs []fetchedConfig -} - -type fetchedConfig struct { - kind string - source string - referenced bool + State *state.State } // Run executes the stage of the given name. It returns true if the stage @@ -93,10 +88,10 @@ func (e Engine) Run(stageName string) error { e.Logger.Crit("failed to acquire system base config: %v", err) return err } else if err == nil { - e.fetchedConfigs = append(e.fetchedConfigs, fetchedConfig{ - kind: "base", - source: "system", - referenced: false, + e.State.FetchedConfigs = append(e.State.FetchedConfigs, state.FetchedConfig{ + Kind: "base", + Source: "system", + Referenced: false, }) } @@ -138,7 +133,7 @@ func (e Engine) Run(stageName string) error { defer e.Logger.PopPrefix() fullConfig := latest.Merge(baseConfig, latest.Merge(systemBaseConfig, cfg)) - err = stages.Get(stageName).Create(e.Logger, e.Root, *e.Fetcher).Run(fullConfig) + err = stages.Get(stageName).Create(e.Logger, e.Root, *e.Fetcher, e.State).Run(fullConfig) if err == resource.ErrNeedNet && stageName == "fetch-offline" { err = e.signalNeedNet() if err != nil { @@ -164,18 +159,18 @@ func (e Engine) Run(stageName string) error { // logStructuredJournalEntry logs information related to // a user/base config into the systemd journal log. -func logStructuredJournalEntry(cfgInfo fetchedConfig) error { +func logStructuredJournalEntry(cfgInfo state.FetchedConfig) error { ignitionInfo := map[string]string{ - "IGNITION_CONFIG_TYPE": cfgInfo.kind, - "IGNITION_CONFIG_SRC": cfgInfo.source, - "IGNITION_CONFIG_REFERENCED": strconv.FormatBool(cfgInfo.referenced), + "IGNITION_CONFIG_TYPE": cfgInfo.Kind, + "IGNITION_CONFIG_SRC": cfgInfo.Source, + "IGNITION_CONFIG_REFERENCED": strconv.FormatBool(cfgInfo.Referenced), "MESSAGE_ID": ignitionFetchedConfigMsgId, } referenced := "" - if cfgInfo.referenced { + if cfgInfo.Referenced { referenced = "referenced " } - msg := fmt.Sprintf("fetched %s%s config from %q", referenced, cfgInfo.kind, cfgInfo.source) + msg := fmt.Sprintf("fetched %s%s config from %q", referenced, cfgInfo.Kind, cfgInfo.Source) if err := journal.Send(msg, journal.PriInfo, ignitionInfo); err != nil { return err } @@ -193,7 +188,7 @@ func (e *Engine) acquireConfig(stageName string) (cfg types.Config, err error) { // if we've successfully fetched and cached the configs, log about them if err == nil { - for _, cfgInfo := range e.fetchedConfigs { + for _, cfgInfo := range e.State.FetchedConfigs { if logerr := logStructuredJournalEntry(cfgInfo); logerr != nil { e.Logger.Info("failed to log systemd journal entry: %v", logerr) } @@ -324,10 +319,10 @@ func (e *Engine) fetchProviderConfig() (types.Config, error) { return types.Config{}, err } - e.fetchedConfigs = append(e.fetchedConfigs, fetchedConfig{ - kind: "user", - source: providerKey, - referenced: false, + e.State.FetchedConfigs = append(e.State.FetchedConfigs, state.FetchedConfig{ + Kind: "user", + Source: providerKey, + Referenced: false, }) // Replace the HTTP client in the fetcher to be configured with the @@ -429,10 +424,10 @@ func (e *Engine) fetchReferencedConfig(cfgRef types.Resource) (types.Config, err e.Logger.Debug("fetched referenced config from data url with SHA512: %s", hex.EncodeToString(hash[:])) } - e.fetchedConfigs = append(e.fetchedConfigs, fetchedConfig{ - kind: "user", - source: u.Path, - referenced: true, + e.State.FetchedConfigs = append(e.State.FetchedConfigs, state.FetchedConfig{ + Kind: "user", + Source: u.Path, + Referenced: true, }) if err := util.AssertValid(cfgRef.Verification, rawCfg); err != nil { diff --git a/internal/exec/stages/disks/disks.go b/internal/exec/stages/disks/disks.go index 5d2237bc3..3908a762c 100644 --- a/internal/exec/stages/disks/disks.go +++ b/internal/exec/stages/disks/disks.go @@ -28,6 +28,7 @@ import ( "github.com/coreos/ignition/v2/internal/exec/util" "github.com/coreos/ignition/v2/internal/log" "github.com/coreos/ignition/v2/internal/resource" + "github.com/coreos/ignition/v2/internal/state" "github.com/coreos/ignition/v2/internal/systemd" ) @@ -41,12 +42,13 @@ func init() { type creator struct{} -func (creator) Create(logger *log.Logger, root string, f resource.Fetcher) stages.Stage { +func (creator) Create(logger *log.Logger, root string, f resource.Fetcher, state *state.State) stages.Stage { return &stage{ Util: util.Util{ DestDir: root, Logger: logger, Fetcher: f, + State: state, }, } } diff --git a/internal/exec/stages/disks/luks.go b/internal/exec/stages/disks/luks.go index 649d098ed..77ecc24e2 100644 --- a/internal/exec/stages/disks/luks.go +++ b/internal/exec/stages/disks/luks.go @@ -25,7 +25,6 @@ import ( "os" "os/exec" "path" - "path/filepath" "strings" "github.com/coreos/ignition/v2/config/util" @@ -33,6 +32,8 @@ import ( "github.com/coreos/ignition/v2/internal/distro" execUtil "github.com/coreos/ignition/v2/internal/exec/util" "github.com/coreos/ignition/v2/internal/resource" + + "github.com/vincent-petithory/dataurl" ) var ( @@ -104,6 +105,8 @@ func (s *stage) createLuks(config types.Config) error { return err } + s.State.LuksPersistKeyFiles = make(map[string]string) + for _, luks := range config.Storage.Luks { // TODO: allow Ignition generated KeyFiles for // non-clevis devices that can be persisted. @@ -111,15 +114,17 @@ func (s *stage) createLuks(config types.Config) error { // track whether Ignition creates the KeyFile // so that it can be removed var ignitionCreatedKeyFile bool - // create keyfile inside of tmpfs, it will be copied to the - // sysroot by the files stage - if err := os.MkdirAll(distro.LuksInitramfsKeyFilePath(), 0700); err != nil { - return fmt.Errorf("creating directory for keyfile: %v", err) + // create keyfile, remove on the way out + keyFile, err := ioutil.TempFile("", "ignition-luks-") + if err != nil { + return fmt.Errorf("creating keyfile: %w", err) } - keyFilePath := filepath.Join(distro.LuksInitramfsKeyFilePath(), luks.Name) + keyFilePath := keyFile.Name() + keyFile.Close() + defer os.Remove(keyFilePath) devAlias := execUtil.DeviceAlias(*luks.Device) if util.NilOrEmpty(luks.KeyFile.Source) { - // create a keyfile + // generate keyfile contents key, err := randHex(4096) if err != nil { return fmt.Errorf("generating keyfile: %v", err) @@ -316,17 +321,21 @@ func (s *stage) createLuks(config types.Config) error { } } - // assume the user does not want a key file & remove it for clevis based devices if ignitionCreatedKeyFile && luks.Clevis.IsPresent() { + // assume the user does not want the generated key & remove it if _, err := s.Logger.LogCmd( exec.Command(distro.CryptsetupCmd(), "luksRemoveKey", devAlias, keyFilePath), "removing key file for %v", luks.Name, ); err != nil { return fmt.Errorf("removing key file from luks device: %v", err) } - if err := os.Remove(keyFilePath); err != nil { - return fmt.Errorf("removing key file: %v", err) + } else { + // store the key to be persisted into the real root + key, err := ioutil.ReadFile(keyFilePath) + if err != nil { + return fmt.Errorf("failed to read keyfile %q: %w", keyFilePath, err) } + s.State.LuksPersistKeyFiles[luks.Name] = dataurl.EncodeBytes(key) } } diff --git a/internal/exec/stages/fetch/fetch.go b/internal/exec/stages/fetch/fetch.go index 2b8b50216..3c65ecb8b 100644 --- a/internal/exec/stages/fetch/fetch.go +++ b/internal/exec/stages/fetch/fetch.go @@ -24,6 +24,7 @@ import ( "github.com/coreos/ignition/v2/internal/exec/util" "github.com/coreos/ignition/v2/internal/log" "github.com/coreos/ignition/v2/internal/resource" + "github.com/coreos/ignition/v2/internal/state" ) const ( @@ -36,11 +37,12 @@ func init() { type creator struct{} -func (creator) Create(logger *log.Logger, root string, _ resource.Fetcher) stages.Stage { +func (creator) Create(logger *log.Logger, root string, _ resource.Fetcher, state *state.State) stages.Stage { return &stage{ Util: util.Util{ DestDir: root, Logger: logger, + State: state, }, } } diff --git a/internal/exec/stages/fetch_offline/fetch-offline.go b/internal/exec/stages/fetch_offline/fetch-offline.go index 3bac0c851..92e3c1804 100644 --- a/internal/exec/stages/fetch_offline/fetch-offline.go +++ b/internal/exec/stages/fetch_offline/fetch-offline.go @@ -28,6 +28,7 @@ import ( executil "github.com/coreos/ignition/v2/internal/exec/util" "github.com/coreos/ignition/v2/internal/log" "github.com/coreos/ignition/v2/internal/resource" + "github.com/coreos/ignition/v2/internal/state" "github.com/coreos/ignition/v2/internal/util" ) @@ -41,11 +42,12 @@ func init() { type creator struct{} -func (creator) Create(logger *log.Logger, root string, _ resource.Fetcher) stages.Stage { +func (creator) Create(logger *log.Logger, root string, _ resource.Fetcher, state *state.State) stages.Stage { return &stage{ Util: executil.Util{ DestDir: root, Logger: logger, + State: state, }, } } diff --git a/internal/exec/stages/files/files.go b/internal/exec/stages/files/files.go index 7c0dea4c1..e08514403 100644 --- a/internal/exec/stages/files/files.go +++ b/internal/exec/stages/files/files.go @@ -25,6 +25,7 @@ import ( "github.com/coreos/ignition/v2/internal/exec/util" "github.com/coreos/ignition/v2/internal/log" "github.com/coreos/ignition/v2/internal/resource" + "github.com/coreos/ignition/v2/internal/state" ) const ( @@ -41,12 +42,13 @@ func init() { type creator struct{} -func (creator) Create(logger *log.Logger, root string, f resource.Fetcher) stages.Stage { +func (creator) Create(logger *log.Logger, root string, f resource.Fetcher, state *state.State) stages.Stage { return &stage{ Util: util.Util{ DestDir: root, Logger: logger, Fetcher: f, + State: state, }, } } @@ -85,6 +87,10 @@ func (s stage) Run(config types.Config) error { return fmt.Errorf("creating crypttab entries: %v", err) } + if err := s.createResultFile(); err != nil { + return fmt.Errorf("creating result file: %v", err) + } + if err := s.relabelFiles(); err != nil { return fmt.Errorf("failed to handle relabeling: %v", err) } diff --git a/internal/exec/stages/files/filesystemEntries.go b/internal/exec/stages/files/filesystemEntries.go index d788085ca..8bcd58974 100644 --- a/internal/exec/stages/files/filesystemEntries.go +++ b/internal/exec/stages/files/filesystemEntries.go @@ -15,6 +15,7 @@ package files import ( + "encoding/json" "fmt" "io/ioutil" "os" @@ -22,6 +23,7 @@ import ( "path/filepath" "sort" "strings" + "time" cutil "github.com/coreos/ignition/v2/config/util" "github.com/coreos/ignition/v2/config/v3_4_experimental/types" @@ -42,7 +44,6 @@ func (s *stage) createCrypttabEntries(config types.Config) error { s.Logger.PushPrefix("createCrypttabEntries") defer s.Logger.PopPrefix() - mode := 0600 path, err := s.JoinPath("/etc/crypttab") if err != nil { return fmt.Errorf("building crypttab filepath: %v", err) @@ -52,7 +53,7 @@ func (s *stage) createCrypttabEntries(config types.Config) error { Path: path, }, types.FileEmbedded1{ - Mode: &mode, + Mode: cutil.IntToPtr(0600), }, } extrafiles := []filesystemEntry{} @@ -70,12 +71,11 @@ func (s *stage) createCrypttabEntries(config types.Config) error { if !luks.Clevis.IsPresent() { keyfile = filepath.Join(distro.LuksRealRootKeyFilePath(), luks.Name) - // Copy keyfile from /run to sysroot - contents, err := ioutil.ReadFile(filepath.Join(distro.LuksInitramfsKeyFilePath(), luks.Name)) - if err != nil { - return fmt.Errorf("reading keyfile for %s: %v", luks.Name, err) + // Write keyfile into sysroot + contentsUri, ok := s.State.LuksPersistKeyFiles[luks.Name] + if !ok { + return fmt.Errorf("missing persisted keyfile for %s", luks.Name) } - contentsUri := dataurl.EncodeBytes(contents) keyfilePath, err := s.JoinPath(keyfile) if err != nil { return fmt.Errorf("building keyfile path: %v", err) @@ -88,7 +88,7 @@ func (s *stage) createCrypttabEntries(config types.Config) error { Contents: types.Resource{ Source: &contentsUri, }, - Mode: &mode, + Mode: cutil.IntToPtr(0600), }, }) } @@ -101,7 +101,6 @@ func (s *stage) createCrypttabEntries(config types.Config) error { // already exist) to be mode 0700 rather than auto-creating it at the default directory // permission if len(extrafiles) > 0 { - dirMode := 0700 realpath, err := s.JoinPath(distro.LuksRealRootKeyFilePath()) if err != nil { return fmt.Errorf("building keyfile dir path: %v", err) @@ -116,7 +115,7 @@ func (s *stage) createCrypttabEntries(config types.Config) error { Path: realpath, }, types.DirectoryEmbedded1{ - Mode: &dirMode, + Mode: cutil.IntToPtr(0700), }, }, }, extrafiles...) @@ -127,10 +126,81 @@ func (s *stage) createCrypttabEntries(config types.Config) error { if err := s.createEntries(extrafiles); err != nil { return fmt.Errorf("adding luks related files: %v", err) } - // delete the entire keyfiles folder in /run/ so that the keyfiles are stored on + // delete the persisted keyfiles from state so that the keyfiles are stored on // only the root device which can be encrypted - if err := os.RemoveAll(distro.LuksInitramfsKeyFilePath()); err != nil { - return fmt.Errorf("removing initramfs keyfiles: %v", err) + s.State.LuksPersistKeyFiles = nil + return nil +} + +// createResultFile creates a report recording some details about the +// Ignition run. +func (s *stage) createResultFile() error { + if distro.ResultFilePath() == "" { + return nil + } + + s.Logger.PushPrefix("createResultFile") + defer s.Logger.PopPrefix() + + bootIDBytes, err := ioutil.ReadFile(distro.BootIDPath()) + if err != nil { + return fmt.Errorf("reading boot ID: %w", err) + } + + result := struct { + ProvisioningBootID string `json:"provisioningBootID"` + ProvisioningDate string `json:"provisioningDate"` + UserConfigProvided bool `json:"userConfigProvided"` + }{ + ProvisioningBootID: strings.TrimSpace(string(bootIDBytes)), + ProvisioningDate: time.Now().Format(time.RFC3339), + } + for _, config := range s.State.FetchedConfigs { + if config.Kind == "user" { + result.UserConfigProvided = true + break + } + } + + data, err := json.MarshalIndent(result, "", " ") + if err != nil { + return fmt.Errorf("marshaling result file: %w", err) + } + data = append(data, '\n') + + path, err := s.JoinPath(distro.ResultFilePath()) + if err != nil { + return fmt.Errorf("building result file path: %w", err) + } + contentsUri := dataurl.EncodeBytes(data) + entries := []filesystemEntry{ + // create containing directory with restrictive permissions + dirEntry{ + types.Node{ + Path: filepath.Dir(path), + }, + types.DirectoryEmbedded1{ + Mode: cutil.IntToPtr(0700), + }, + }, + fileEntry{ + types.Node{ + Path: path, + // Ignition is not designed to run twice, + // but don't introduce a hard failure if it + // does + Overwrite: cutil.BoolToPtr(true), + }, + types.FileEmbedded1{ + Contents: types.Resource{ + Source: &contentsUri, + }, + Mode: cutil.IntToPtr(0600), + }, + }, + } + if err := s.createEntries(entries); err != nil { + return fmt.Errorf("adding result file: %v", err) } return nil } diff --git a/internal/exec/stages/kargs/kargs.go b/internal/exec/stages/kargs/kargs.go index 31e7bcb7f..24bd047cf 100644 --- a/internal/exec/stages/kargs/kargs.go +++ b/internal/exec/stages/kargs/kargs.go @@ -24,6 +24,7 @@ import ( "github.com/coreos/ignition/v2/internal/exec/util" "github.com/coreos/ignition/v2/internal/log" "github.com/coreos/ignition/v2/internal/resource" + "github.com/coreos/ignition/v2/internal/state" ) const ( @@ -36,12 +37,13 @@ func init() { type creator struct{} -func (creator) Create(logger *log.Logger, root string, f resource.Fetcher) stages.Stage { +func (creator) Create(logger *log.Logger, root string, f resource.Fetcher, state *state.State) stages.Stage { return &stage{ Util: util.Util{ DestDir: root, Logger: logger, Fetcher: f, + State: state, }, } } diff --git a/internal/exec/stages/mount/mount.go b/internal/exec/stages/mount/mount.go index fe90ec455..d9fdcf771 100644 --- a/internal/exec/stages/mount/mount.go +++ b/internal/exec/stages/mount/mount.go @@ -33,6 +33,7 @@ import ( "github.com/coreos/ignition/v2/internal/exec/util" "github.com/coreos/ignition/v2/internal/log" "github.com/coreos/ignition/v2/internal/resource" + "github.com/coreos/ignition/v2/internal/state" ) const ( @@ -45,11 +46,12 @@ func init() { type creator struct{} -func (creator) Create(logger *log.Logger, root string, f resource.Fetcher) stages.Stage { +func (creator) Create(logger *log.Logger, root string, f resource.Fetcher, state *state.State) stages.Stage { return &stage{ Util: util.Util{ DestDir: root, Logger: logger, + State: state, }, } } diff --git a/internal/exec/stages/stages.go b/internal/exec/stages/stages.go index 271a5d377..7b1b6ea82 100644 --- a/internal/exec/stages/stages.go +++ b/internal/exec/stages/stages.go @@ -19,6 +19,7 @@ import ( "github.com/coreos/ignition/v2/internal/log" "github.com/coreos/ignition/v2/internal/registry" "github.com/coreos/ignition/v2/internal/resource" + "github.com/coreos/ignition/v2/internal/state" ) // Stage is responsible for actually executing a stage of the configuration. @@ -30,7 +31,7 @@ type Stage interface { // StageCreator is responsible for instantiating a particular stage given a // logger and root path under the root partition. type StageCreator interface { - Create(logger *log.Logger, root string, f resource.Fetcher) Stage + Create(logger *log.Logger, root string, f resource.Fetcher, state *state.State) Stage Name() string } diff --git a/internal/exec/stages/umount/umount.go b/internal/exec/stages/umount/umount.go index 7926ea750..e0b1d3ef4 100644 --- a/internal/exec/stages/umount/umount.go +++ b/internal/exec/stages/umount/umount.go @@ -27,6 +27,7 @@ import ( "github.com/coreos/ignition/v2/internal/exec/util" "github.com/coreos/ignition/v2/internal/log" "github.com/coreos/ignition/v2/internal/resource" + "github.com/coreos/ignition/v2/internal/state" "golang.org/x/sys/unix" ) @@ -41,11 +42,12 @@ func init() { type creator struct{} -func (creator) Create(logger *log.Logger, root string, f resource.Fetcher) stages.Stage { +func (creator) Create(logger *log.Logger, root string, f resource.Fetcher, state *state.State) stages.Stage { return &stage{ Util: util.Util{ DestDir: root, Logger: logger, + State: state, }, } } diff --git a/internal/exec/util/util.go b/internal/exec/util/util.go index c6955a3c5..a0149e1c0 100644 --- a/internal/exec/util/util.go +++ b/internal/exec/util/util.go @@ -20,6 +20,7 @@ import ( "github.com/coreos/ignition/v2/internal/log" "github.com/coreos/ignition/v2/internal/resource" + "github.com/coreos/ignition/v2/internal/state" ) // Util encapsulates logging and destdir indirection for the util methods. @@ -27,6 +28,7 @@ type Util struct { DestDir string // directory prefix to use in applying fs paths. Fetcher resource.Fetcher *log.Logger + State *state.State } // SplitPath splits /a/b/c/d into [a, b, c, d] diff --git a/internal/main.go b/internal/main.go index c4e6af169..175efb4ad 100644 --- a/internal/main.go +++ b/internal/main.go @@ -31,6 +31,7 @@ import ( _ "github.com/coreos/ignition/v2/internal/exec/stages/umount" "github.com/coreos/ignition/v2/internal/log" "github.com/coreos/ignition/v2/internal/platform" + "github.com/coreos/ignition/v2/internal/state" "github.com/coreos/ignition/v2/internal/version" ) @@ -43,6 +44,7 @@ func main() { platform platform.Name root string stage stages.Name + stateFile string version bool logToStdout bool }{} @@ -54,6 +56,7 @@ func main() { flag.Var(&flags.platform, "platform", fmt.Sprintf("current platform. %v", platform.Names())) flag.StringVar(&flags.root, "root", "/", "root of the filesystem") flag.Var(&flags.stage, "stage", fmt.Sprintf("execution stage. %v", stages.Names())) + flag.StringVar(&flags.stateFile, "state-file", "/run/ignition/state", "where to store internal state") flag.BoolVar(&flags.version, "version", false, "print the version and exit") flag.BoolVar(&flags.logToStdout, "log-to-stdout", false, "log to stdout instead of the system log when set") @@ -92,6 +95,11 @@ func main() { logger.Crit("failed to generate fetcher: %s", err) os.Exit(3) } + state, err := state.Load(flags.stateFile) + if err != nil { + logger.Crit("reading state: %s", err) + os.Exit(3) + } engine := exec.Engine{ Root: flags.root, FetchTimeout: flags.fetchTimeout, @@ -100,6 +108,7 @@ func main() { ConfigCache: flags.configCache, PlatformConfig: platformConfig, Fetcher: &fetcher, + State: &state, } err = engine.Run(flags.stage.String()) @@ -110,5 +119,9 @@ func main() { logger.Crit("Ignition failed: %v", err.Error()) os.Exit(1) } + if err := engine.State.Save(flags.stateFile); err != nil { + logger.Crit("writing state: %v", err) + os.Exit(1) + } logger.Info("Ignition finished successfully") } diff --git a/internal/state/state.go b/internal/state/state.go new file mode 100644 index 000000000..347ee545b --- /dev/null +++ b/internal/state/state.go @@ -0,0 +1,70 @@ +// Copyright 2021 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package state + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" +) + +type State struct { + // Information about configs fetched by the fetch stages. Used + // when writing the result file in files stage. + FetchedConfigs []FetchedConfig `json:"fetchedConfigs"` + // Key files generated during LUKS setup in disks stage, which need + // to be written out during files stage. files stage removes them + // from state afterward to avoid leaking the keys into the running + // system. + LuksPersistKeyFiles map[string]string `json:"luksPersistKeyFiles"` +} + +type FetchedConfig struct { + Kind string `json:"kind"` + Source string `json:"source"` + Referenced bool `json:"referenced"` +} + +func Load(path string) (State, error) { + data, err := ioutil.ReadFile(path) + if os.IsNotExist(err) { + // valid; return empty struct + return State{}, nil + } else if err != nil { + return State{}, fmt.Errorf("reading state file: %w", err) + } + var state State + if err = json.Unmarshal(data, &state); err != nil { + return State{}, fmt.Errorf("parsing state file: %w", err) + } + return state, nil +} + +func (s *State) Save(path string) error { + data, err := json.Marshal(s) + if err != nil { + return fmt.Errorf("serializing state file: %w", err) + } + data = append(data, '\n') + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { + return fmt.Errorf("creating directory for state file: %w", err) + } + if err := ioutil.WriteFile(path, data, 0600); err != nil { + return fmt.Errorf("writing state file: %w", err) + } + return nil +} diff --git a/tests/filesystem.go b/tests/filesystem.go index 5cf77e92b..b8680bcd0 100644 --- a/tests/filesystem.go +++ b/tests/filesystem.go @@ -155,7 +155,8 @@ func runIgnition(t *testing.T, ctx context.Context, stage, root, cwd string, app args := []string{"-platform", "file", "-stage", stage, "-root", root, "-log-to-stdout", "-config-cache", filepath.Join(cwd, "ignition.json"), - "-neednet", filepath.Join(cwd, "neednet")} + "-neednet", filepath.Join(cwd, "neednet"), + "-state-file", filepath.Join(cwd, "state")} cmd := exec.CommandContext(ctx, "ignition", args...) if cmd == nil { return fmt.Errorf("exec.CommandContext() returned nil")