From 452bf66dbe88bb5618b384d1f90acccf1d0e720e Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 24 Mar 2020 19:55:09 +0000 Subject: [PATCH] WIP: support for `fetch --detect-offline-config` In some cases, we want to make a decision in the initramfs based on whether or not an Ignition config was provided at all. A good example of this is for live ISOs, we only want to turn on networking if a config was provided: https://github.com/coreos/ignition-dracut/issues/94 So the idea is that we'd end up running `ignition fetch --detect-offline-config` as part of a systemd generator, which could then take futher steps like pulling in `network-online.target` if a config was provided. --- internal/exec/engine.go | 44 +++++++++++++++++++++++++++ internal/main.go | 5 +++ internal/providers/cmdline/cmdline.go | 8 +++++ internal/providers/file/file.go | 19 +++++++++++- internal/providers/providers.go | 1 + 5 files changed, 76 insertions(+), 1 deletion(-) diff --git a/internal/exec/engine.go b/internal/exec/engine.go index 862831a2a..dc981e045 100644 --- a/internal/exec/engine.go +++ b/internal/exec/engine.go @@ -24,6 +24,8 @@ import ( "os" "time" + "github.com/coreos/ignition/v2/internal/providers/file" + "github.com/coreos/ignition/v2/config" "github.com/coreos/ignition/v2/config/shared/errors" latest "github.com/coreos/ignition/v2/config/v3_1_experimental" @@ -54,6 +56,8 @@ type Engine struct { Root string PlatformConfig platform.Config Fetcher *resource.Fetcher + + DetectOfflineConfig string } // Run executes the stage of the given name. It returns true if the stage @@ -67,6 +71,26 @@ func (e Engine) Run(stageName string) error { Ignition: types.Ignition{Version: types.MaxVersion.String()}, } + if stageName == "fetch" && e.DetectOfflineConfig != "" { + var haveConfig bool + // If we already have a config cache, we're done + if _, err := os.Stat(e.ConfigCache); err != nil { + haveConfig = true + } else { + haveConfig, err = e.detectOfflineConfig() + if err != nil { + return err + } + } + if haveConfig { + if err := ioutil.WriteFile(e.DetectOfflineConfig, []byte{}, 0644); err != nil { + return err + } + } + // Note early return + return nil + } + systemBaseConfig, r, err := system.FetchBaseConfig(e.Logger) e.logReport(r) if err != nil && err != providers.ErrNoProvider { @@ -175,6 +199,26 @@ func (e *Engine) acquireConfig() (cfg types.Config, err error) { return } +// acquireConfigOffline only looks at config providers which do not +// require networking. +func (e *Engine) detectOfflineConfig() (bool, error) { + offlineFetchers := []providers.FuncDetectConfig{ + cmdline.DetectConfig, + file.DetectConfig, + } + + for _, fetcher := range offlineFetchers { + exists, err := fetcher(e.Fetcher) + if err != nil { + return false, err + } + if exists { + return true, nil + } + } + return false, nil +} + // fetchProviderConfig returns the externally-provided configuration. It first // checks to see if the command-line option is present. If so, it uses that // source for the configuration. If the command-line option is not present, it diff --git a/internal/main.go b/internal/main.go index f630b917b..944a76d57 100644 --- a/internal/main.go +++ b/internal/main.go @@ -42,11 +42,14 @@ func main() { stage stages.Name version bool logToStdout bool + + detectOfflineConfig string }{} flag.BoolVar(&flags.clearCache, "clear-cache", false, "clear any cached config") flag.StringVar(&flags.configCache, "config-cache", "/run/ignition.json", "where to cache the config") flag.DurationVar(&flags.fetchTimeout, "fetch-timeout", exec.DefaultFetchTimeout, "initial duration for which to wait for config") + flag.StringVar(&flags.detectOfflineConfig, "detect-config-provided", "", "If a config is provided, create a file at this path") 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())) @@ -95,6 +98,8 @@ func main() { ConfigCache: flags.configCache, PlatformConfig: platformConfig, Fetcher: &fetcher, + + DetectOfflineConfig: flags.detectOfflineConfig, } err = engine.Run(flags.stage.String()) diff --git a/internal/providers/cmdline/cmdline.go b/internal/providers/cmdline/cmdline.go index b92e8bc41..0c12e0250 100644 --- a/internal/providers/cmdline/cmdline.go +++ b/internal/providers/cmdline/cmdline.go @@ -36,6 +36,14 @@ const ( cmdlineUrlFlag = "ignition.config.url" ) +func DetectConfig(f *resource.Fetcher) (bool, error) { + url, err := readCmdline(f.Logger) + if err != nil { + return false, err + } + return url != nil, nil +} + func FetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) { url, err := readCmdline(f.Logger) if err != nil { diff --git a/internal/providers/file/file.go b/internal/providers/file/file.go index 0f6227799..b85dc372f 100644 --- a/internal/providers/file/file.go +++ b/internal/providers/file/file.go @@ -30,12 +30,29 @@ const ( defaultFilename = "config.ign" ) -func FetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) { +func getPath(f *resource.Fetcher) string { filename := os.Getenv(cfgFilenameEnvVar) if filename == "" { filename = defaultFilename f.Logger.Info("using default filename") } + return filename +} + +func DetectConfig(f *resource.Fetcher) (bool, error) { + filename := getPath(f) + _, err := os.Stat(filename) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + return true, nil +} + +func FetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) { + filename := getPath(f) f.Logger.Info("using config file at %q", filename) rawConfig, err := ioutil.ReadFile(filename) diff --git a/internal/providers/providers.go b/internal/providers/providers.go index 1d6c08e31..ee9aa1923 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -28,6 +28,7 @@ var ( ErrNoProvider = errors.New("config provider was not online") ) +type FuncDetectConfig func(f *resource.Fetcher) (bool, error) type FuncFetchConfig func(f *resource.Fetcher) (types.Config, report.Report, error) type FuncNewFetcher func(logger *log.Logger) (resource.Fetcher, error) type FuncPostStatus func(stageName string, f resource.Fetcher, e error) error