Skip to content

Commit 423e203

Browse files
authored
Adds support for per-component new version checks (#1113)
Signed-off-by: lujunsan <[email protected]>
1 parent 9e5de1d commit 423e203

File tree

4 files changed

+244
-152
lines changed

4 files changed

+244
-152
lines changed

pkg/operator/telemetry/telemetry_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ func (m *mockVersionClient) GetLatestVersion(_ string, _ string) (string, error)
2929
return m.version, nil
3030
}
3131

32+
func (*mockVersionClient) GetComponent() string {
33+
return "operator"
34+
}
35+
3236
func TestService_CheckForUpdates(t *testing.T) {
3337
t.Parallel()
3438
scheme := runtime.NewScheme()

pkg/updates/checker.go

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ func NewUpdateChecker(versionClient VersionClient) (UpdateChecker, error) {
2929
return nil, fmt.Errorf("unable to access update file path %w", err)
3030
}
3131

32-
// Check to see if the file already exists. Read the instance ID from the
32+
// Get component name for component-specific data
33+
component := getComponentFromVersionClient(versionClient)
34+
35+
// Check to see if the file already exists. Read the instance ID and component-specific data from the
3336
// file if it does. If it doesn't exist, create a new instance ID.
3437
var instanceID, previousVersion string
3538
// #nosec G304: File path is not configurable at this time.
@@ -56,6 +59,7 @@ func NewUpdateChecker(versionClient VersionClient) (UpdateChecker, error) {
5659
updateFilePath: path,
5760
versionClient: versionClient,
5861
previousAPIResponse: previousVersion,
62+
component: component,
5963
}, nil
6064
}
6165

@@ -64,10 +68,16 @@ const (
6468
updateInterval = 4 * time.Hour
6569
)
6670

71+
// componentInfo represents component-specific update timing information.
72+
type componentInfo struct {
73+
LastCheck time.Time `json:"last_check"`
74+
}
75+
6776
// updateFile represents the structure of the update file.
6877
type updateFile struct {
69-
InstanceID string `json:"instance_id"`
70-
LatestVersion string `json:"latest_version"`
78+
InstanceID string `json:"instance_id"`
79+
LatestVersion string `json:"latest_version"`
80+
Components map[string]componentInfo `json:"components"`
7181
}
7282

7383
type defaultUpdateChecker struct {
@@ -76,43 +86,67 @@ type defaultUpdateChecker struct {
7686
previousAPIResponse string
7787
updateFilePath string
7888
versionClient VersionClient
89+
component string
7990
}
8091

8192
func (d *defaultUpdateChecker) CheckLatestVersion() error {
82-
// Check if the update file exists.
83-
// Ignore the error if the file doesn't exist - we'll create it later.
84-
fileInfo, err := os.Stat(d.updateFilePath)
93+
// Read the current update file to get component-specific data
94+
var currentFile updateFile
95+
// #nosec G304: File path is not configurable at this time.
96+
rawContents, err := os.ReadFile(d.updateFilePath)
8597
if err != nil && !os.IsNotExist(err) {
86-
return fmt.Errorf("failed to stat update file: %w", err)
98+
return fmt.Errorf("failed to read update file: %w", err)
99+
}
100+
101+
// Initialize file structure if it doesn't exist or is empty
102+
if os.IsNotExist(err) || len(rawContents) == 0 {
103+
currentFile = updateFile{
104+
InstanceID: d.instanceID,
105+
Components: make(map[string]componentInfo),
106+
}
107+
} else {
108+
if err := json.Unmarshal(rawContents, &currentFile); err != nil {
109+
return fmt.Errorf("failed to deserialize update file: %w", err)
110+
}
111+
112+
// Initialize components map if it doesn't exist (for backward compatibility)
113+
if currentFile.Components == nil {
114+
currentFile.Components = make(map[string]componentInfo)
115+
}
116+
117+
// Use the instance ID from file, but fallback to the one we generated
118+
if currentFile.InstanceID == "" {
119+
currentFile.InstanceID = d.instanceID
120+
}
87121
}
88122

89-
// Check if we need to make an API request based on file modification time.
90-
if fileInfo != nil && time.Since(fileInfo.ModTime()) < updateInterval {
91-
// If it is too soon - notify the user if we already know there is
92-
// an update, then exit.
93-
notifyIfUpdateAvailable(d.currentVersion, d.previousAPIResponse)
94-
return nil
123+
// Check component-specific timing
124+
if componentData, exists := currentFile.Components[d.component]; exists {
125+
if time.Since(componentData.LastCheck) < updateInterval {
126+
// If it is too soon - notify the user if we already know there is
127+
// an update, then exit.
128+
notifyIfUpdateAvailable(d.currentVersion, currentFile.LatestVersion)
129+
return nil
130+
}
95131
}
96132

97-
// If the update file is stale or does not exist - get the latest version
133+
// If the component data is stale or does not exist - get the latest version
98134
// from the API.
99-
latestVersion, err := d.versionClient.GetLatestVersion(d.instanceID, d.currentVersion)
135+
latestVersion, err := d.versionClient.GetLatestVersion(currentFile.InstanceID, d.currentVersion)
100136
if err != nil {
101137
return fmt.Errorf("failed to check for updates: %w", err)
102138
}
103139

104140
notifyIfUpdateAvailable(d.currentVersion, latestVersion)
105141

106-
// Rewrite the update file with the latest result.
107-
// If we really wanted to optimize this, we could skip updating the file
108-
// if the version is the same as the current version, but update the
109-
// modification time.
110-
newFileContents := updateFile{
111-
InstanceID: d.instanceID,
112-
LatestVersion: latestVersion,
142+
// Update shared latest version and component-specific timing
143+
currentFile.LatestVersion = latestVersion
144+
currentFile.Components[d.component] = componentInfo{
145+
LastCheck: time.Now().UTC(),
113146
}
114147

115-
updatedData, err := json.Marshal(newFileContents)
148+
// Write the updated file
149+
updatedData, err := json.Marshal(currentFile)
116150
if err != nil {
117151
return fmt.Errorf("failed to marshal updated data: %w", err)
118152
}
@@ -124,6 +158,11 @@ func (d *defaultUpdateChecker) CheckLatestVersion() error {
124158
return nil
125159
}
126160

161+
// getComponentFromVersionClient extracts the component name from a VersionClient.
162+
func getComponentFromVersionClient(versionClient VersionClient) string {
163+
return versionClient.GetComponent()
164+
}
165+
127166
func notifyIfUpdateAvailable(current, latest string) {
128167
// Print a meaningful message for people running local builds.
129168
if strings.HasPrefix(current, "build-") {

0 commit comments

Comments
 (0)