-
Notifications
You must be signed in to change notification settings - Fork 382
version: add update notifications, json output
#2421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
b1bed7e
e43c2fa
9fb1bfc
ce2d68c
0acd69f
3bf548c
740d22c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,10 @@ Below is a list of environment variables available in TFLint. | |
| - Configure the config file path. See [Configuring TFLint](./config.md). | ||
| - `TFLINT_PLUGIN_DIR` | ||
| - Configure the plugin directory. See [Configuring Plugins](./plugins.md). | ||
| - `TFLINT_DISABLE_VERSION_CHECK` | ||
| - Disable version update notifications when running `tflint --version`. Set to `1` to disable. | ||
| - `GITHUB_TOKEN` | ||
| - (Optional) Used for authenticated GitHub API requests when checking for updates and downloading plugins. Increases the rate limit from 60 to 5000 requests per hour. Useful if you encounter rate limit errors. You can obtain a token by creating a [GitHub personal access token](https://github.com/settings/tokens); no special scopes are required. | ||
|
Comment on lines
+13
to
+14
|
||
| - `TFLINT_EXPERIMENTAL` | ||
| - Enable experimental features. Note that experimental features are subject to change without notice. Currently only [Keyless Verification](./plugins.md#keyless-verification-experimental) are supported. | ||
| - `TF_VAR_name` | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,86 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| package versioncheck | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "log" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "os" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "path/filepath" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "time" | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // CacheTTL is how long cached version info is considered valid | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| CacheTTL = 48 * time.Hour | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // CacheEntry represents a cached version check result | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| type CacheEntry struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| LatestVersion string `json:"latest_version"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| CheckedAt time.Time `json:"checked_at"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // IsExpired returns whether the cache entry has exceeded its TTL | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (c *CacheEntry) IsExpired() bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return time.Since(c.CheckedAt) > CacheTTL | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // loadCache reads and parses the cache file | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Returns nil if cache doesn't exist or is invalid | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| func loadCache() (*CacheEntry, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| cachePath, err := getCachePath() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| data, err := os.ReadFile(cachePath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if os.IsNotExist(err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.Printf("[DEBUG] No cache file found at %s", cachePath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| var entry CacheEntry | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := json.Unmarshal(data, &entry); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.Printf("[DEBUG] Failed to parse cache file: %s", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return &entry, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // saveCache writes the cache entry to disk | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| func saveCache(entry *CacheEntry) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| cachePath, err := getCachePath() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return err | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Ensure directory exists | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| cacheDir := filepath.Dir(cachePath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := os.MkdirAll(cacheDir, 0755); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return err | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| data, err := json.MarshalIndent(entry, "", " ") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return err | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := os.WriteFile(cachePath, data, 0644); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return err | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+71
to
+74
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := os.WriteFile(cachePath, data, 0644); err != nil { | |
| return err | |
| } | |
| // Write to a temp file, then atomically rename | |
| tmpFile, err := os.CreateTemp(cacheDir, "version_check_cache_*.tmp") | |
| if err != nil { | |
| return err | |
| } | |
| defer func() { | |
| tmpFile.Close() | |
| os.Remove(tmpFile.Name()) // Clean up temp file if rename fails | |
| }() | |
| if _, err := tmpFile.Write(data); err != nil { | |
| return err | |
| } | |
| if err := tmpFile.Sync(); err != nil { | |
| return err | |
| } | |
| if err := tmpFile.Close(); err != nil { | |
| return err | |
| } | |
| if err := os.Rename(tmpFile.Name(), cachePath); err != nil { | |
| return err | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| package versioncheck | ||
|
|
||
| import ( | ||
| "testing" | ||
| "time" | ||
| ) | ||
|
|
||
| func TestCacheEntry_IsExpired(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| checkedAt time.Time | ||
| want bool | ||
| }{ | ||
| { | ||
| name: "fresh cache (1 hour old)", | ||
| checkedAt: time.Now().Add(-1 * time.Hour), | ||
| want: false, | ||
| }, | ||
| { | ||
| name: "fresh cache (24 hours old)", | ||
| checkedAt: time.Now().Add(-24 * time.Hour), | ||
| want: false, | ||
| }, | ||
| { | ||
| name: "expired cache (49 hours old)", | ||
| checkedAt: time.Now().Add(-49 * time.Hour), | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "just expired (48 hours + 1 minute)", | ||
| checkedAt: time.Now().Add(-48*time.Hour - 1*time.Minute), | ||
| want: true, | ||
| }, | ||
| { | ||
| name: "just fresh (47 hours)", | ||
| checkedAt: time.Now().Add(-47 * time.Hour), | ||
| want: false, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| entry := &CacheEntry{ | ||
| LatestVersion: "0.60.0", | ||
| CheckedAt: tt.checkedAt, | ||
| } | ||
|
|
||
| got := entry.IsExpired() | ||
| if got != tt.want { | ||
| t.Errorf("CacheEntry.IsExpired() = %v, want %v", got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
GITHUB_TOKENenvironment variable is used for authenticated GitHub API requests (as mentioned in lines 23-27 of versioncheck/github.go) but is not documented in the environment variables documentation. Consider adding documentation for this optional variable explaining: