diff --git a/app/api.go b/app/api.go new file mode 100644 index 0000000..8f5ddc3 --- /dev/null +++ b/app/api.go @@ -0,0 +1,42 @@ +package app + +import ( + "github.com/foundriesio/fioconfig/internal" + "github.com/foundriesio/fioconfig/sotatoml" +) + +type App internal.App + +var NotModifiedError = internal.NotModifiedError + +// NewAppWithConfig creates a new App instance with the provided SOTA configuration +// that can perform all the basic Fioconfig operations. +func NewAppWithConfig(sota *sotatoml.AppConfig, secretsDir string, unsafeHandlers bool) (*App, error) { + app, err := internal.NewAppWithConfig(sota, secretsDir, unsafeHandlers, false) + if err != nil { + return nil, err + } + return (*App)(app), nil +} + +// Extract extracts secrets from the encrypted configuration into the secrets directory. +// must be called once. A common way to do this is a systemd "oneshot" service after +// NetworkManager is up. +func (a *App) Extract() error { + return (*internal.App)(a).Extract() +} + +// CheckIn checks with the device gateway for the lastest configuration. If there +// are changes, it applies them locally. If the config on the server is unchanged, +// it returns NotModifiedError. This function also will try and run any pending +// "init" functions required by Fioconfig that have not yet been run. +func (a *App) CheckIn() error { + return (*internal.App)(a).CheckIn() +} + +// RunAndReport runs a command specified by name with args, and collects +// artifacts found under artifactsDir. It reports the results back to the +// device-gateway's fiotest API under the test identified by testId. +func (a *App) RunAndReport(name, testId, artifactsDir string, args []string) error { + return (*internal.App)(a).RunAndReport(name, testId, artifactsDir, args) +} diff --git a/internal/app.go b/internal/app.go index ad9bba1..fbc1718 100644 --- a/internal/app.go +++ b/internal/app.go @@ -37,7 +37,6 @@ type App struct { SecretsDir string configUrl string - configPaths []string unsafeHandlers bool sota *sotatoml.AppConfig @@ -61,7 +60,7 @@ func createClient(cfg *sotatoml.AppConfig) (*http.Client, CryptoHandler) { return client, NewEciesPkcs11Handler(extra, tlsCfg.Certificates[0].PrivateKey) } -func NewApp(configPaths []string, secrets_dir string, unsafeHandlers, testing bool) (*App, error) { +func NewApp(configPaths []string, secretsDir string, unsafeHandlers, testing bool) (*App, error) { if len(configPaths) == 0 { configPaths = sotatoml.DEF_CONFIG_ORDER } @@ -69,6 +68,10 @@ func NewApp(configPaths []string, secrets_dir string, unsafeHandlers, testing bo if err != nil { return nil, fmt.Errorf("unable to parse sota.toml: %w", err) } + return NewAppWithConfig(sota, secretsDir, unsafeHandlers, testing) +} + +func NewAppWithConfig(sota *sotatoml.AppConfig, secretsDir string, unsafeHandlers, testing bool) (*App, error) { // Assert we have a sane configuration _, crypto := createClient(sota) crypto.Close() @@ -84,9 +87,8 @@ func NewApp(configPaths []string, secrets_dir string, unsafeHandlers, testing bo app := App{ StorageDir: storagePath, EncryptedConfig: filepath.Join(storagePath, "config.encrypted"), - SecretsDir: secrets_dir, + SecretsDir: secretsDir, configUrl: url, - configPaths: configPaths, sota: sota, unsafeHandlers: unsafeHandlers, exitFunc: os.Exit, @@ -172,7 +174,7 @@ func (a *App) runOnChanged(fname string, fullpath string, onChanged []string) { cmd := exec.Command(onChanged[0], onChanged[1:]...) cmd.Env = append(os.Environ(), "CONFIG_FILE="+fullpath) cmd.Env = append(cmd.Env, "STORAGE_DIR="+a.StorageDir) - cmd.Env = append(cmd.Env, "SOTA_DIR="+strings.Join(a.configPaths, ",")) + cmd.Env = append(cmd.Env, "SOTA_DIR="+strings.Join(a.sota.SearchPaths(), ",")) cmd.Env = append(cmd.Env, "FIOCONFIG_BIN="+path) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr diff --git a/internal/app_test.go b/internal/app_test.go index 5a1a6e7..0d25352 100644 --- a/internal/app_test.go +++ b/internal/app_test.go @@ -401,7 +401,7 @@ func TestCheckGood(t *testing.T) { assertFile(t, random, nil) if barChangedStat, err := os.Stat(barChanged); err != nil { t.Fatal(err) - } else if barChangedTime == barChangedStat.ModTime() { + } else if barChangedTime.Equal(barChangedStat.ModTime()) { t.Fatalf("A barChanged modstamp is ought to change") } }) diff --git a/internal/rotate.go b/internal/rotate.go index d1ee18c..5e96234 100644 --- a/internal/rotate.go +++ b/internal/rotate.go @@ -38,7 +38,7 @@ func NewCertRotationHandler(app *App, stateFile, estServer string) *CertRotation state := &CertRotationState{EstServer: estServer} return &CertRotationHandler{ stateHandler[*CertRotationState]{ - stateContext: newStateContext[*CertRotationState](app, stateFile, state), + stateContext: newStateContext(app, stateFile, state), steps: []certRotationStep{ estStep{}, lockStep{}, diff --git a/sotatoml/app_config.go b/sotatoml/app_config.go index 65ebb2f..f9f9a7a 100644 --- a/sotatoml/app_config.go +++ b/sotatoml/app_config.go @@ -28,7 +28,8 @@ type cfgFile struct { } type AppConfig struct { - cfgs []*cfgFile + searchPaths []string + cfgs []*cfgFile } // NewAppConfig parses config files as per: @@ -70,10 +71,15 @@ func NewAppConfig(configPaths []string) (*AppConfig, error) { } } - cfg := AppConfig{cfgs: keys} + cfg := AppConfig{cfgs: keys, searchPaths: configPaths} return &cfg, nil } +// SearchPaths returns the list of config paths used to build this AppConfig +func (c AppConfig) SearchPaths() []string { + return c.searchPaths +} + func (c AppConfig) CombinedConfig() (*toml.Tree, error) { var combined map[string]any for i := len(c.cfgs) - 1; i >= 0; i-- {