Skip to content
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

Check for new krew versions once a day #490

Closed

Conversation

corneliusweig
Copy link
Contributor

Fixes #480

This will check once a day if a new version of krew is available.
The timestamp of the last run is stored in KREW_HOME/last_update_check.
As this is a convenience function, silently ignore errors if they occur.

To opt out of this feature, set the KREW_NO_UPGRADE_CHECK env variable.

This will check once a day if a new version of krew is available.
The timestamp of the last run is stored in KREW_HOME/last_update_check.
As this is a convenience function, silently ignore errors if they occur.
@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Feb 2, 2020
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: corneliusweig

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Feb 2, 2020
@codecov-io
Copy link

codecov-io commented Feb 2, 2020

Codecov Report

Merging #490 into master will increase coverage by 0.55%.
The diff coverage is 70%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #490      +/-   ##
==========================================
+ Coverage   56.47%   57.03%   +0.55%     
==========================================
  Files          19       20       +1     
  Lines         926      966      +40     
==========================================
+ Hits          523      551      +28     
- Misses        350      357       +7     
- Partials       53       58       +5
Impacted Files Coverage Δ
internal/updatecheck/update.go 70% <70%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 7842e57...8466e32. Read the comment docs.

cmd/krew/cmd/root.go Outdated Show resolved Hide resolved
docs/USER_GUIDE.md Outdated Show resolved Hide resolved
internal/assertion/update.go Outdated Show resolved Hide resolved
internal/assertion/update.go Outdated Show resolved Hide resolved
internal/assertion/update.go Outdated Show resolved Hide resolved

func saveTimestamp(file string) {
klog.V(4).Info("Saving timestamp for last version check")
timestamp := time.Now().Format(time.RFC1123)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just use unix time as the persisted format?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean seconds since epoch? I prefer a more readable format. I changed to something which still easy to manipulate.

internal/assertion/update.go Outdated Show resolved Hide resolved
internal/assertion/update.go Outdated Show resolved Hide resolved
internal/assertion/update.go Outdated Show resolved Hide resolved
internal/assertion/update.go Outdated Show resolved Hide resolved
This code is racey now, because the global variable is accessed
without synchronization. Since this is a convenience feature,
this is acceptable.
- Improve code structure
- Wrap errors instead of logging
- Improve wording of upgrade message
- Improve code structure
- Wrap errors instead of logging
- Improve wording of upgrade message
- Change timestamp format

klog.V(4).Infof("Found latest tag %q", latestTag)
return latestTag
if version.GitTag() == latestTag {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nicer to actually use our semver pkg to do a > check here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or rather, maybe don't do the version tag here?

I feel like the CheckVersion isn't matching what this method does with reading last_update_check time + comparison with current version.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not do any semver parsing here. In tests, version.GitTag returns unknown which would not work.
Besides, we only allow the remote version to increase. So checking for equality also does the job.

}
saveTimestamp(f)

if version.GitTag() == latestTag {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also worth keeping in mind that sometimes version.GitTag() might return malformed or empty string that isn't a semver.

if we actually parse that higher in the stack, we don't even have to fetchLatestTag().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean to say that the update check should not run for development builds? That would make perfect sense.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not? If I want to be in the edge, I would care updating even more than a regular user.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not:

  • if you are developing krew, you are usually ahead of the latest release. So the "upgrade" notification would recommend a downgrade :)
  • the CI runs non-release builds of krew in integration tests. We don't need upgrade notifications there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean to say that the update check should not run for development builds? That would make perfect sense.

Not just development builds.
For example, until recently homebrew formula did not have -ldflags that stamp the GitTag into the binary.
Also think of people running go get sigs.k8s.io/krew/cmd/krew, it will not have a GitTag in it.

For those cases, just fail to parse version as semver, and move on.

  • if you are developing krew, you are usually ahead of the latest release. So the "upgrade" notification would recommend a downgrade :)

thats why (1) parse strings into SemVer (2) use semver.LessThan to compare instead of !=.

if err != nil {
return "", errors.Wrapf(err, "could not determine latest tag")
}
saveTimestamp(f)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need defer saveTimestamp() earlier in this func.

last_update_check shouldn't just count successful checks, but rather "completed attempts".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is that? If a check fails, we should retry soon instead of waiting the full cool-down. So without defer sounds more reasonable to me.

@ahmetb
Copy link
Member

ahmetb commented Feb 3, 2020

So right now we check every 24 hours.
And we record "checked" status only if:

  1. request completed before krew does its job (probably not for krew version, krew info, krew search etc) AND
  2. request was successful

we need to be careful about the impact of this.

}
}

func Test_fetchTag(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually something more valuable to test than "github returning broken json" would be "github unreachable" case.

it's far more likely to happen (Little Snitch, and other corp firewalls etc.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but I don't see how I can simulate this situation in tests. From the docs of http.Get:

An error is returned if there were too many redirects or if there
was an HTTP protocol error.

So I don't see how I could possibly cause an error here :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah well.. I can just use a bad address, which will cause an HTTP error.

klog.V(3).Info("Latest tag is same as ours.")
return "", nil
}
return color.New(color.Bold).Sprintf(upgradeNotification, version.GitTag(), latestTag), nil
Copy link
Member

@ahmetb ahmetb Feb 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this method doesn't have a good signature.
Returning strings with colors isn't great.

let's change it to something like this

func UpdateAvailable(basePath string, currentVersion semver.Version) (semver.Version, exists bool, error)

then we can curate the message outside.

}
}

func Test_fetchTag(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a TestFetchTag that actually queries GitHub?

I know making network calls isn't a great idea during unit tests, but it would help us notice if the Github API breaks somehow under us.

@corneliusweig
Copy link
Contributor Author

we need to be careful about the impact of this.

If your really think we could run into problems, let's reset the cooldown also after failed API requests. As long as the cooldown is not too large, this is fine with me.

content, err := ioutil.ReadFile(file)
if err != nil {
klog.V(4).Infof("Could not read timestamp file %q", file)
return time.Unix(0, 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not default? time.Time{}?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it’s not supposed to be used when err exists, it practically doesn’t matter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering if there was a special reason.

Yes, end result doesn't change but it's like []type vs nil, would be more idiomatic.

// will only be emitted once a day.
func CheckVersion(basePath string) (string, error) {
f := filepath.Join(basePath, "last_update_check")
if lastCheck := loadTimestamp(f); time.Since(lastCheck).Hours() <= 24 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you put the range to the top as constant?

an idea might be introducing another environment variable to customize it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just hardcoded const is good

@ahmetb
Copy link
Member

ahmetb commented Feb 4, 2020

If your really think we could run into problems, let's reset the cooldown also after failed API requests.

on the contrary, if we fail, I think that still should count as a "completed attempt" –and we should not do any more requests in the next 24h IMO.

We want people to eventually update; not right away. However, if a check isn't complete (i.e. krew exists before we can request+print warning), we should not record that and run it again next time.

the `KREW_NO_UPGRADE_CHECK` environment variable. To permanently disable this,
add the following to your `~/.bashrc`, `~/.bash_profile`, or `~/.zshrc`:

export KREW_NO_UPGRADE_CHECK=1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An idea: since we don't know how it will affect users in the beginnning, we might make it opt-in to use at first.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-1. we discussed at #480 that this isn't sending PII. It's basically almost like a git clone that we already do. I don't wish users to learn yet another thing. Let's keep the interaction surface minimal.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, sounds good.

Btw, sorry for skipping the actual discussion and opening them here again.

@@ -104,6 +104,15 @@ without any arguments:
Since `krew` itself is a plugin also managed through `krew`, running the upgrade
command may also upgrade your `krew` version.

### Krew upgrade check
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is too high up.

Let's move this below to the main user journey and call it ## Disabling update checks

@corneliusweig
Copy link
Contributor Author

A major part of the discussion has revolved around the file where the timestamp of the last check is stored. I created an alternate version of this feature where checks are done at random here #494.

So I'll close this if favor of the other PR.
/close

@k8s-ci-robot
Copy link
Contributor

@corneliusweig: Closed this PR.

In response to this:

A major part of the discussion has revolved around the file where the timestamp of the last check is stored. I created an alternate version of this feature where checks are done at random here #494.

So I'll close this if favor of the other PR.
/close

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Proposal: occassionally show available upgrades for Krew
5 participants