From 8de3af6a2948eaece4d351d170f09964f48ce0c4 Mon Sep 17 00:00:00 2001 From: Patrick Dawkins Date: Thu, 2 Jan 2025 00:23:31 +0000 Subject: [PATCH] Various config install improvements --- commands/config_install.go | 45 +++++++----- commands/root.go | 40 +++++------ go.mod | 1 + go.sum | 2 + internal/config/alt/alt.go | 15 ++-- internal/config/alt/fs.go | 2 +- .../config/alt/{auto_update.go => update.go} | 62 ++++++++--------- .../{auto_update_test.go => update_test.go} | 69 ++++++++++++++----- internal/config/schema.go | 9 ++- 9 files changed, 141 insertions(+), 104 deletions(-) rename internal/config/alt/{auto_update.go => update.go} (58%) rename internal/config/alt/{auto_update_test.go => update_test.go} (51%) diff --git a/commands/config_install.go b/commands/config_install.go index 95367567..c7784fd3 100644 --- a/commands/config_install.go +++ b/commands/config_install.go @@ -18,15 +18,19 @@ import ( var configInstallCommand = &cobra.Command{ Use: "config:install [flags] [url]", - Short: "Installs a new CLI instance from a configuration URL", + Short: "Installs an alternative CLI, downloading new configuration from a URL", Args: cobra.ExactArgs(1), RunE: runConfigInstall, } func runConfigInstall(cmd *cobra.Command, args []string) error { - cmd.PrintErrln("Downloading alternative CLI configuration.") + cmd.PrintErrln("Downloading and validating new CLI configuration...") - newCnfNode, newCnfStruct, err := alt.FetchConfig(cmd.Context(), args[0]) + urlStr := args[0] + if !strings.Contains(urlStr, "://") { + urlStr = "https://" + urlStr + } + newCnfNode, newCnfStruct, err := alt.FetchConfig(cmd.Context(), urlStr) if err != nil { return err } @@ -37,7 +41,7 @@ func runConfigInstall(cmd *cobra.Command, args []string) error { } // Find the directory and file paths. - binDir, err := alt.FindBinDir() + executableDir, err := alt.FindBinDir() if err != nil { return err } @@ -46,9 +50,9 @@ func runConfigInstall(cmd *cobra.Command, args []string) error { return err } configFilePath := filepath.Join(configDir, newExecutable+".yaml") - binFilePath := filepath.Join(binDir, newExecutable) + executableFilePath := filepath.Join(executableDir, newExecutable) - if path, err := exec.LookPath(newExecutable); err == nil && path != binFilePath { + if path, err := exec.LookPath(newExecutable); err == nil && path != executableFilePath { return fmt.Errorf( "cannot install config: an executable with the same name already exists at another location: %s", path, @@ -75,13 +79,13 @@ func runConfigInstall(cmd *cobra.Command, args []string) error { if err := os.MkdirAll(configDir, 0o755); err != nil { return err } - if err := os.MkdirAll(binDir, 0o755); err != nil { + if err := os.MkdirAll(executableDir, 0o755); err != nil { return err } if err := os.WriteFile(configFilePath, configContent, 0o600); err != nil { return err } - if err := os.WriteFile(binFilePath, []byte(executableContent), 0o700); err != nil { //nolint:gosec + if err := os.WriteFile(executableFilePath, []byte(executableContent), 0o700); err != nil { //nolint:gosec return err } @@ -96,20 +100,23 @@ func runConfigInstall(cmd *cobra.Command, args []string) error { } }() - cmd.PrintErrln() cmd.PrintErrf( - "The following files have been created:\n"+ + "\nThe following files have been created:\n"+ " - Configuration: %s\n"+ " - Executable: %s\n", color.CyanString(replaceHomeDir(configFilePath)), - color.CyanString(replaceHomeDir(binFilePath)), + color.CyanString(replaceHomeDir(executableFilePath)), ) - cmd.PrintErrln() - cmd.PrintErrln("The configuration file will be auto-updated periodically in the background.") - isInPath, err := alt.InPath(binDir) + if newCnfStruct.Updates.Check { + cmd.PrintErrln("\nThe configuration file will be auto-updated periodically in the background.") + } else { + cmd.PrintErrln("\nEnable automatic updates to keep the configuration file up to date.") + } + + isInPath, err := alt.InPath(executableDir) if err != nil { - return fmt.Errorf("could not determine if the bin directory is in the PATH: %w", err) + return fmt.Errorf("could not determine if the executable directory is in the PATH: %w", err) } envVarName := "PATH" @@ -120,17 +127,17 @@ func runConfigInstall(cmd *cobra.Command, args []string) error { cmd.PrintErrln() if isInPath { - cmd.PrintErrln("Run the new executable with:", color.GreenString(filepath.Base(binFilePath))) + cmd.PrintErrln("Run the new CLI with:", color.GreenString(filepath.Base(executableFilePath))) } else { cmd.PrintErrf( "Add the following directory to your %s: %s\n", envVarName, - color.YellowString(replaceHomeDir(binDir)), + color.YellowString(replaceHomeDir(executableDir)), ) cmd.PrintErrln() cmd.PrintErrln( - "Then you will be able to run the new executable with:", - color.YellowString(filepath.Base(binFilePath)), + "Then you will be able to run the new CLI with:", + color.YellowString(filepath.Base(executableFilePath)), ) } diff --git a/commands/root.go b/commands/root.go index d850e48b..9b3b44ed 100644 --- a/commands/root.go +++ b/commands/root.go @@ -56,6 +56,8 @@ func newRootCommand(cnf *config.Config, assets *vendorization.VendorAssets) *cob if viper.GetBool("quiet") && !viper.GetBool("debug") && !viper.GetBool("verbose") { viper.Set("no-interaction", true) cmd.SetErr(io.Discard) + } else { + cmd.SetErr(color.Error) } if viper.GetBool("version") { versionCommand.Run(cmd, []string{}) @@ -67,10 +69,10 @@ func newRootCommand(cnf *config.Config, assets *vendorization.VendorAssets) *cob updateMessageChan <- rel }() } - if alt.ShouldAutoUpdate(cnf) { + if alt.ShouldUpdate(cnf) { go func() { - if err := alt.AutoUpdate(cmd.Context(), cnf, debugLog); err != nil { - cmd.PrintErrln("Error auto-updating config:", err) + if err := alt.Update(cmd.Context(), cnf, debugLog); err != nil { + cmd.PrintErrln("Error updating config:", color.RedString(err.Error())) } }() } @@ -88,13 +90,13 @@ func newRootCommand(cnf *config.Config, assets *vendorization.VendorAssets) *cob Stdin: cmd.InOrStdin(), } if err := c.Init(); err != nil { - _, _ = fmt.Fprint(color.Error, color.RedString(err.Error())) + cmd.PrintErrf("%s\n", color.RedString(err.Error())) os.Exit(1) return } if err := c.Exec(cmd.Context(), os.Args[1:]...); err != nil { - _, _ = fmt.Fprint(color.Error, color.RedString(err.Error())) + cmd.PrintErrf("%s\n", color.RedString(err.Error())) exitCode := 1 var execErr *exec.ExitError if errors.As(err, &execErr) { @@ -103,11 +105,11 @@ func newRootCommand(cnf *config.Config, assets *vendorization.VendorAssets) *cob os.Exit(exitCode) } }, - PersistentPostRun: func(_ *cobra.Command, _ []string) { - checkShellConfigLeftovers(cnf) + PersistentPostRun: func(cmd *cobra.Command, _ []string) { + checkShellConfigLeftovers(cnf, cmd.ErrOrStderr()) select { case rel := <-updateMessageChan: - printUpdateMessage(rel, cnf) + printUpdateMessage(rel, cnf, cmd.ErrOrStderr()) default: } }, @@ -169,7 +171,7 @@ func newRootCommand(cnf *config.Config, assets *vendorization.VendorAssets) *cob } // checkShellConfigLeftovers checks .zshrc and .bashrc for any leftovers from the legacy CLI -func checkShellConfigLeftovers(cnf *config.Config) { +func checkShellConfigLeftovers(cnf *config.Config, stdErr io.Writer) { start := fmt.Sprintf("# BEGIN SNIPPET: %s configuration", cnf.Application.Name) end := "# END SNIPPET" shellConfigSnippet := regexp.MustCompile(regexp.QuoteMeta(start) + "(?s).+?" + regexp.QuoteMeta(end)) @@ -195,23 +197,23 @@ func checkShellConfigLeftovers(cnf *config.Config) { } if shellConfigSnippet.Match(shellConfig) { - fmt.Fprintf(color.Error, "%s Your %s file contains code that is no longer needed for the New %s\n", + fmt.Fprintf(stdErr, "%s Your %s file contains code that is no longer needed for the New %s\n", color.YellowString("Warning:"), shellConfigFile, cnf.Application.Name, ) - fmt.Fprintf(color.Error, "%s %s\n", color.YellowString("Please remove the following lines from:"), shellConfigFile) - fmt.Fprintf(color.Error, "\t%s\n", strings.ReplaceAll(string(shellConfigSnippet.Find(shellConfig)), "\n", "\n\t")) + fmt.Fprintf(stdErr, "%s %s\n", color.YellowString("Please remove the following lines from:"), shellConfigFile) + fmt.Fprintf(stdErr, "\t%s\n", strings.ReplaceAll(string(shellConfigSnippet.Find(shellConfig)), "\n", "\n\t")) } } } -func printUpdateMessage(newRelease *internal.ReleaseInfo, cnf *config.Config) { +func printUpdateMessage(newRelease *internal.ReleaseInfo, cnf *config.Config, stdErr io.Writer) { if newRelease == nil { return } - fmt.Fprintf(color.Error, "\n\n%s %s → %s\n", + fmt.Fprintf(stdErr, "\n\n%s %s → %s\n", color.YellowString(fmt.Sprintf("A new release of the %s is available:", cnf.Application.Name)), color.CyanString(config.Version), color.CyanString(newRelease.Version), @@ -219,20 +221,18 @@ func printUpdateMessage(newRelease *internal.ReleaseInfo, cnf *config.Config) { executable, err := os.Executable() if err == nil && cnf.Wrapper.HomebrewTap != "" && isUnderHomebrew(executable) { - fmt.Fprintf( - color.Error, + fmt.Fprintf(stdErr, "To upgrade, run: brew update && brew upgrade %s\n", color.YellowString(cnf.Wrapper.HomebrewTap), ) } else if cnf.Wrapper.GitHubRepo != "" { - fmt.Fprintf( - color.Error, + fmt.Fprintf(stdErr, "To upgrade, follow the instructions at: https://github.com/%s#upgrade\n", cnf.Wrapper.GitHubRepo, ) } - fmt.Fprintf(color.Error, "%s\n\n", color.YellowString(newRelease.URL)) + fmt.Fprintf(stdErr, "%s\n\n", color.YellowString(newRelease.URL)) } func isUnderHomebrew(binary string) bool { @@ -256,5 +256,5 @@ func debugLog(format string, v ...any) { } prefix := color.New(color.ReverseVideo).Sprintf("DEBUG") - _, _ = fmt.Fprintf(color.Error, prefix+" "+strings.TrimSpace(format)+"\n", v...) + fmt.Fprintf(color.Error, prefix+" "+strings.TrimSpace(format)+"\n", v...) } diff --git a/go.mod b/go.mod index d79b4f55..da4dbccb 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/wk8/go-ordered-map/v2 v2.1.8 golang.org/x/crypto v0.31.0 + golang.org/x/mod v0.18.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index db90fb41..1b0096e2 100644 --- a/go.sum +++ b/go.sum @@ -160,6 +160,8 @@ golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ss golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= diff --git a/internal/config/alt/alt.go b/internal/config/alt/alt.go index e405ce9b..04bc9b08 100644 --- a/internal/config/alt/alt.go +++ b/internal/config/alt/alt.go @@ -6,7 +6,6 @@ import ( "io" "net/http" "net/url" - "strings" "time" "gopkg.in/yaml.v3" @@ -19,9 +18,6 @@ import ( // A comment and some metadata are added to the cnfNode. // A "cnfStruct" is also returned to allow reading some keys. func FetchConfig(ctx context.Context, urlStr string) (cnfNode *yaml.Node, cnfStruct *config.Config, err error) { - if !strings.Contains(urlStr, "://") { - urlStr = "https://" + urlStr - } resp, err := fetch(ctx, urlStr) if err != nil { return nil, nil, fmt.Errorf("failed to fetch config: %w", err) @@ -82,9 +78,10 @@ func processConfig(b []byte, urlStr string) (cnfNode *yaml.Node, cnfStruct *conf // Add a comment to the document. cnfNode.HeadComment = fmt.Sprintf( - "CLI configuration.\n"+ - "This file will be updated automatically.\n"+ + "%s configuration.\n"+ + "Do not edit this file, as it will be replaced with updated versions automatically.\n"+ "Source URL: %s\nDownloaded at: %s\n", + cnfStruct.Application.Name, urlStr, metadata.DownloadedAt.Format(time.RFC3339), ) @@ -130,9 +127,13 @@ func deleteDocumentKey(doc *yaml.Node, toDelete string) error { } func fetch(ctx context.Context, urlStr string) (*http.Response, error) { - if _, err := url.Parse(urlStr); err != nil { + u, err := url.Parse(urlStr) + if err != nil { return nil, fmt.Errorf("invalid URL: %w", err) } + if u.Scheme != "https" && u.Hostname() != "127.0.0.1" { + return nil, fmt.Errorf("invalid config URL scheme %s (https required): %s", u.Scheme, urlStr) + } httpClient := &http.Client{Timeout: 10 * time.Second} req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, http.NoBody) if err != nil { diff --git a/internal/config/alt/fs.go b/internal/config/alt/fs.go index 1a94db21..16104d0e 100644 --- a/internal/config/alt/fs.go +++ b/internal/config/alt/fs.go @@ -1,4 +1,4 @@ -// Package alt manages instances of alternate CLI configurations. +// Package alt manages instances of alternative CLI configurations. package alt import ( diff --git a/internal/config/alt/auto_update.go b/internal/config/alt/update.go similarity index 58% rename from internal/config/alt/auto_update.go rename to internal/config/alt/update.go index 74e3412a..dd931063 100644 --- a/internal/config/alt/auto_update.go +++ b/internal/config/alt/update.go @@ -6,35 +6,30 @@ import ( "os" "time" - "github.com/gofrs/flock" + "golang.org/x/mod/semver" "gopkg.in/yaml.v3" "github.com/platformsh/cli/internal/config" "github.com/platformsh/cli/internal/state" ) -// ShouldAutoUpdate returns whether the AutoUpdate function may be run on configuration. -func ShouldAutoUpdate(cnf *config.Config) bool { +// ShouldUpdate returns whether the Update function may be run on configuration. +func ShouldUpdate(cnf *config.Config) bool { return cnf.Updates.Check && cnf.Source.Type == config.SourceTypeFile && cnf.Source.Filename != "" && cnf.Metadata.URL != "" } -const defaultUpdateInterval = time.Hour * 6 - -// AutoUpdate checks for configuration updates, when appropriate. +// Update checks for configuration updates, when appropriate. // It updates the config file using an exclusive lock. // The "cnf" pointer will NOT be updated with the new configuration. -func AutoUpdate(ctx context.Context, cnf *config.Config, debugLog func(fmt string, i ...any)) error { +func Update(ctx context.Context, cnf *config.Config, debugLog func(fmt string, i ...any)) error { s, err := state.Load(cnf) if err != nil { return err } - interval := time.Second * time.Duration(cnf.Metadata.UpdateInterval) - if interval == 0 { - interval = defaultUpdateInterval - } + interval := time.Second * time.Duration(cnf.Updates.CheckInterval) lastChecked := time.Unix(s.ConfigUpdates.LastChecked, 0) if time.Since(lastChecked) < interval { debugLog("Config updates checked recently (%v ago)", time.Since(lastChecked).Truncate(time.Second)) @@ -48,27 +43,10 @@ func AutoUpdate(ctx context.Context, cnf *config.Config, debugLog func(fmt strin return fmt.Errorf("no config URL available") } cnfPath := cnf.Source.Filename + tmpPath := cnfPath + ".tmp" + oldPath := cnfPath + ".bak" - lock := flock.New(cnfPath) - locked, err := lock.TryLock() - if err != nil { - return fmt.Errorf("failed to lock config file: %w", err) - } - defer func() { - if err := lock.Unlock(); err != nil { - debugLog("Failed to unlock config file: %s", err) - } - }() - if !locked { - return err - } - - f, err := os.OpenFile(cnfPath, os.O_WRONLY, 0o600) - if err != nil { - return fmt.Errorf("failed to open config file for writing: %w", err) - } - defer f.Close() - stat, err := f.Stat() + stat, err := os.Stat(cnfPath) if err != nil { return fmt.Errorf("could not stat config file %s: %w", cnfPath, err) } @@ -89,16 +67,30 @@ func AutoUpdate(ctx context.Context, cnf *config.Config, debugLog func(fmt strin if err != nil { return err } - if !cnf.Metadata.UpdatedAt.IsZero() && !newCnfStruct.Metadata.UpdatedAt.IsZero() && - cnf.Metadata.UpdatedAt.After(newCnfStruct.Metadata.UpdatedAt) { - debugLog("Skipping auto update: current config updated after remote config") + if !newCnfStruct.Metadata.UpdatedAt.IsZero() && + !newCnfStruct.Metadata.UpdatedAt.After(cnf.Metadata.UpdatedAt) { + debugLog("Config is already up to date (updated at %v)", cnf.Metadata.UpdatedAt) + return nil + } + if newCnfStruct.Metadata.Version != "" && + (cnf.Metadata.Version == newCnfStruct.Metadata.Version || + // TODO find a version comparison tool that does not require a "v" prefix + semver.Compare(cnf.Metadata.Version, newCnfStruct.Metadata.Version) > 0) { + debugLog("Config is already up to date (version %s)", cnf.Metadata.Version) return nil } b, err := yaml.Marshal(newCnfNode) if err != nil { return err } - if _, err := f.Write(b); err != nil { + if err := os.WriteFile(tmpPath, b, 0o600); err != nil { + return err + } + if err := os.Rename(cnfPath, oldPath); err != nil { + return err + } + debugLog("Backed up config file to: %s", oldPath) + if err := os.Rename(tmpPath, cnfPath); err != nil { return err } debugLog("Automatically updated config file: %s", cnfPath) diff --git a/internal/config/alt/auto_update_test.go b/internal/config/alt/update_test.go similarity index 51% rename from internal/config/alt/auto_update_test.go rename to internal/config/alt/update_test.go index 4c3b7deb..e53e8f7a 100644 --- a/internal/config/alt/auto_update_test.go +++ b/internal/config/alt/update_test.go @@ -15,13 +15,13 @@ import ( "github.com/platformsh/cli/internal/config" "github.com/platformsh/cli/internal/config/alt" + "github.com/platformsh/cli/internal/state" ) -func TestAutoUpdate(t *testing.T) { +func TestUpdate(t *testing.T) { tempDir := t.TempDir() // Copy test config to a temporary directory, and fake its modification time. - testConfigModified := time.Now().Add(-time.Hour) testConfigFilename := filepath.Join(tempDir, "config.yaml") err := os.WriteFile(testConfigFilename, testConfig, 0o600) require.NoError(t, err) @@ -33,13 +33,20 @@ func TestAutoUpdate(t *testing.T) { err = os.Setenv(cnf.Application.EnvPrefix+"HOME", tempDir) require.NoError(t, err) - // Set up the config to be auto-updated via a test HTTP server. - server := httptest.NewServer(http.FileServer(http.Dir(tempDir))) + // Set up the config to be updated via a test HTTP server. + remoteConfig := testConfig + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/config.yaml" { + _, _ = w.Write(remoteConfig) + return + } + w.WriteHeader(http.StatusNotFound) + })) defer server.Close() cnf.Source.Type = config.SourceTypeFile cnf.Source.Filename = testConfigFilename - cnf.Metadata.UpdateInterval = 1 + cnf.Updates.CheckInterval = 1 cnf.Metadata.URL = server.URL + "/config.yaml" // TODO use test context @@ -52,25 +59,53 @@ func TestAutoUpdate(t *testing.T) { lastLogged = fmt.Sprintf(msg, args...) } - assert.True(t, alt.ShouldAutoUpdate(cnf)) + assert.True(t, alt.ShouldUpdate(cnf)) - err = alt.AutoUpdate(ctx, cnf, logger) + err = alt.Update(ctx, cnf, logger) assert.NoError(t, err) assert.Contains(t, lastLogged, "Config file updated recently") - err = os.Chtimes(testConfigFilename, testConfigModified, testConfigModified) - require.NoError(t, err) + hourAgo := time.Now().Add(-time.Hour) + require.NoError(t, os.Chtimes(testConfigFilename, hourAgo, hourAgo)) - err = alt.AutoUpdate(ctx, cnf, logger) + err = alt.Update(ctx, cnf, logger) assert.NoError(t, err) assert.Contains(t, lastLogged, "Automatically updated config file") - err = alt.AutoUpdate(ctx, cnf, logger) + err = alt.Update(ctx, cnf, logger) assert.NoError(t, err) assert.Contains(t, lastLogged, "Config updates checked recently") + + // Reset the LastChecked time and file modified time. + resetTimes := func() { + s, err := state.Load(cnf) + require.NoError(t, err) + s.ConfigUpdates.LastChecked = 0 + require.NoError(t, state.Save(s, cnf)) + require.NoError(t, os.Chtimes(testConfigFilename, hourAgo, hourAgo)) + } + resetTimes() + + cnf.Metadata.Version = "v1.0.1" + remoteConfig = append(remoteConfig, []byte("\nmetadata: {version: v1.0.1}")...) + err = alt.Update(ctx, cnf, logger) + assert.NoError(t, err) + assert.Contains(t, lastLogged, "Config is already up to date (version v1.0.1)") + + resetTimes() + + updated := time.Now() + cnf.Metadata.Version = "" + cnf.Metadata.UpdatedAt = updated + remoteConfig = testConfig + remoteConfig = append(remoteConfig, + []byte(fmt.Sprintf("\nmetadata: {updated_at: %s}", updated.Add(-time.Minute).Format(time.RFC3339)))...) + err = alt.Update(ctx, cnf, logger) + assert.NoError(t, err) + assert.Contains(t, lastLogged, "Config is already up to date") } -func TestShouldAutoUpdate(t *testing.T) { +func TestShouldUpdate(t *testing.T) { testConfigFilename := "/tmp/mock/path/to/config.yaml" cnf, err := config.FromYAML(testConfig) @@ -80,20 +115,20 @@ func TestShouldAutoUpdate(t *testing.T) { cnf.Source.Type = config.SourceTypeFile cnf.Source.Filename = testConfigFilename cnf.Metadata.URL = "https://example.com/config.yaml" - assert.True(t, alt.ShouldAutoUpdate(cnf)) + assert.True(t, alt.ShouldUpdate(cnf)) cnf.Updates.Check = false - assert.False(t, alt.ShouldAutoUpdate(cnf)) + assert.False(t, alt.ShouldUpdate(cnf)) cnf.Updates.Check = true cnf.Source.Type = config.SourceTypeEmbedded - assert.False(t, alt.ShouldAutoUpdate(cnf)) + assert.False(t, alt.ShouldUpdate(cnf)) cnf.Source.Type = config.SourceTypeFile cnf.Metadata.URL = "" - assert.False(t, alt.ShouldAutoUpdate(cnf)) + assert.False(t, alt.ShouldUpdate(cnf)) cnf.Metadata.URL = "https://example.com/config.yaml" cnf.Source.Filename = "" - assert.False(t, alt.ShouldAutoUpdate(cnf)) + assert.False(t, alt.ShouldUpdate(cnf)) } diff --git a/internal/config/schema.go b/internal/config/schema.go index d50c25a6..907704e3 100644 --- a/internal/config/schema.go +++ b/internal/config/schema.go @@ -74,11 +74,10 @@ type Config struct { // Metadata defines information about the config itself. type Metadata struct { - Version string `validate:"omitempty" yaml:"version,omitempty"` - UpdatedAt time.Time `validate:"omitempty" yaml:"updated_at,omitempty"` - DownloadedAt time.Time `validate:"omitempty" yaml:"downloaded_at,omitempty"` - URL string `validate:"omitempty,url" yaml:"url,omitempty"` - UpdateInterval int `validate:"omitempty" yaml:"update_interval,omitempty"` + Version string `validate:"omitempty" yaml:"version,omitempty"` + UpdatedAt time.Time `validate:"omitempty" yaml:"updated_at,omitempty"` + DownloadedAt time.Time `validate:"omitempty" yaml:"downloaded_at,omitempty"` + URL string `validate:"omitempty,url" yaml:"url,omitempty"` } // applyDefaults applies defaults to config before parsing.