-
Notifications
You must be signed in to change notification settings - Fork 371
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
Dynamic plugins list in the website #658
Dynamic plugins list in the website #658
Conversation
8f53f0d
to
bbeed9c
Compare
/hold |
bbeed9c
to
a0517a7
Compare
/hold cancel Ready to preview at:
Feel free to add commits to my branch. |
This relies on Netlify's serverless functions capability to render plugin count (in the homepage) and plugin list (/docs/plugins) in the website! The serverless function is called "api" and it's accessible on path /.netlify/functions/api/* and has two subpaths that return JSON responses: 1. */pluginCount 2. */plugins This serverless function is built by placing a binary in /site/functions named "api" and the build instruction is in 'netlify.toml' file. Since we get plugin list by calling GitHub API, by default we run without an authentication token. However after this is merged, I will go and add a permissionless $GITHUB_ACCESS_TOKEN that I have created from my account. Unauthenticated GitHub calls are limited to 60 per IP, but since I'll add a personal access token, that should not be an issue. When debugging locally, 60 is also fine. (deploy previews for PRs from non-maintainers will not have an access token available, so they will make unauthenticated calls to GH API.) The responses from the dynamic functions are actually cached in Netlify's CDN and that helps with the rate-limiting as well. I have currently set the CDN cache duration to 1 hour for each response. Please take a look at the previews and let me know if there is anything that doesn't look good. I might delete `generate-plugin-overview` tool and update the current plugins.md in a followup PR. Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
a0517a7
to
66f683b
Compare
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.
This is really nice work! A few nits, but otherwise this is perfect stuff.
Very cool that you found out about netlify functions. This will save us so much pointless work.
I agree that generate-plugin-overview
should be deleted asap as soon as this is merged.
site/content/_index.md
Outdated
@@ -11,7 +11,8 @@ Krew helps you: | |||
- install them on your machine, | |||
- and keep the installed plugins up-to-date. | |||
|
|||
There are [over 90 kubectl plugins][list] currently distributed on Krew. | |||
There are [over <span id="krew-plugin-count">...</span> kubectl plugins][list] |
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.
There are [over <span id="krew-plugin-count">...</span> kubectl plugins][list] | |
There are [<span id="krew-plugin-count">...</span> kubectl plugins][list] |
IIUC, the number is exact?
site/layouts/partials/footer.html
Outdated
fetch('/.netlify/functions/api/pluginCount') | ||
.then(response => response.json()) | ||
.then(response => { | ||
console.log(`${response.data.count} plugins found`); |
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.
I'd omit console.log for the good case. We don't want to spam the console.
site/layouts/partials/footer.html
Outdated
.then(response => response.json()) | ||
.then(response => { | ||
console.log(`${response.data.count} plugins found`); | ||
pluginCountElem.innerText=response.data?.count || 0; |
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.
pluginCountElem.innerText=response.data?.count || 0; | |
pluginCountElem.innerText=response.data?.count || '?'; |
This will make it clearer that there was a failure.
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.
Might do <error>
:D
site/layouts/partials/footer.html
Outdated
@@ -44,5 +44,77 @@ | |||
</script> | |||
{{ end }} | |||
|
|||
<script> | |||
document.addEventListener('DOMContentLoaded', (event) => { | |||
var pluginCountElem = document.getElementById("krew-plugin-count"); |
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.
I'd prefer to use querySelectorAll
instead, and replace all occurrences on the page. I can easily imagine that we will show the plugin count in two different places and then we will need that.
site/layouts/partials/footer.html
Outdated
.then(response => response.json()) | ||
.then(response => { | ||
var count = response.data?.plugins?.length || 0; | ||
console.log(`${count} plugins loaded`); |
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.
nit: no logging?
site/functions/server/main.go
Outdated
for { | ||
select { | ||
case <-ctx.Done(): | ||
return | ||
case url, ok := <-queue: | ||
if !ok { | ||
return | ||
} | ||
p, err := readPlugin(url) | ||
if err != nil { | ||
retErr = err | ||
cancel() | ||
return | ||
} | ||
mu.Lock() | ||
out = append(out, p) | ||
mu.Unlock() | ||
} | ||
} |
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.
When using errgroup, this could be simplified to
for url := range queue {
p, err := readPlugin(url)
if err != nil {
return err
}
mu.Lock()
out = append(out, p)
mu.Unlock()
}
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.
might use errgroup, but I don't see a way to limit parallelism with errgroup.
feel free to add a patch.
site/functions/server/main.go
Outdated
func readPlugin(url string) (*krew.Plugin, error) { | ||
resp, err := http.Get(url) | ||
if err != nil { | ||
return nil, errors.Wrapf(err,"failed to get %s", url) |
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.
nit: run gofmt :)
site/functions/server/main.go
Outdated
if resp.Body != nil { | ||
defer resp.Body.Close() | ||
} |
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.
nit:
if resp.Body != nil { | |
defer resp.Body.Close() | |
} | |
defer resp.Body.Close() |
From the spec:
// The http Client and Transport guarantee that Body is always
// non-nil, even on responses without a body or responses with
// a zero-length body. It is the caller's responsibility to
// close Body.
site/functions/server/main.go
Outdated
if resp.Body != nil { | ||
defer resp.Body.Close() | ||
} | ||
var v krew.Plugin |
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.
nit: move v
right before yaml.Unmarshal
site/functions/server/main.go
Outdated
// To debug locally, you can run this server with -port=:8080 and run "hugo serve" and uncomment this: | ||
mux.Handle("/", httputil.NewSingleHostReverseProxy(&url.URL{Scheme: "http", Host: "localhost:1313"})) |
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 comment reads like this should be commented out?
Anyway, I think it'd be much nicer like that, then no commenting is necessary:
if *port > -1 {
...
}
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.
It can be commented out but I left it on just in case. You're right.
@corneliusweig I think it would be better if you do a pass with your commits on the frontend pieces. My frontend skills are weak at best. :) |
🤔 I see "Allow edits and access to secrets by maintainers" so you should be able to push to my branch. Have you tried over ssh (git://) ? |
So I tried again various approaches---nothing will update this PR. However, now there's also a new branch in krew, called So I think it's easiest if you merge my PR into your PR 🙄 |
Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be | ||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 | ||
gopkg.in/yaml.v2 v2.2.8 | ||
sigs.k8s.io/krew v0.4.0 |
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.
this is a bit worrisome to pin the site to a particular version of master
. (should be ok as long as we keep the types/consts backwards compatible)
this replace
statement works fine locally, but once I uncommented the replace
statement and deploy, it fails to build on Netlify sadly. not sure why.
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.
Yes, it will be annoying if we forget about it. I don't see a better alternative though.
knownHomePages := map[string]string{ | ||
`https://krew.sigs.k8s.io/`: "kubernetes-sigs/krew", | ||
`https://sigs.k8s.io/krew`: "kubernetes-sigs/krew", | ||
`https://kubernetes.github.io/ingress-nginx/kubectl-plugin/`: "kubernetes/ingress-nginx", | ||
`https://kudo.dev/`: "kudobuilder/kudo", | ||
`https://kubevirt.io`: "kubevirt/kubectl-virt-plugin", | ||
`https://popeyecli.io`: "derailed/popeye", | ||
`https://soluble-ai.github.io/kubetap/`: "soluble-ai/kubetap", | ||
} |
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.
@corneliusweig I wonder if we should move this to somewhere like a var
declaration at the top. I suspect it'll get updated frequently. :D
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.
I wonder if we should even go one step further and extract this configuration into non-code. In krew-index-tracker
, I also need this list and it is error-prone to redeploy every time this changes.
Maybe we can keep this map in a very dumb format in a text file inside krew-index
or krew
. Something like
https://krew.sigs.k8s.io/ kubernetes-sigs/krew
https://sigs.k8s.io/krew kubernetes-sigs/krew
...
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.
I'm -1 for a new file/format.
I think we can hardcode these still in a centralized source code file.
We can do a variable on top of main.go for now.
@@ -44,5 +44,58 @@ | |||
</script> | |||
{{ end }} | |||
|
|||
<script> |
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.
@corneliusweig should we move these to a .js file at this point?
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.
Yes, that makes sense to me. We don't have to do it in this PR though. Maybe open a new issue? [[main reason: collaboration on the shared PR seems to be not straightforward]]
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.
ack
</script> | ||
|
||
<script type="module"> | ||
import {html, render} from 'https://unpkg.com/lit-html?module'; |
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.
I must admit I don't fully understand how imports work in the browser so @corneliusweig you’ll probably be on point for maintaining these :D I barely can get the vanilla js working.
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.
Yeah, I'm not too worried about it (support stats).
site/functions/server/main.go
Outdated
} | ||
|
||
func main() { | ||
port := flag.Int("port", -1, `To debug locally set --port=8080 and run "hugo serve"`) |
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.
Running the server locally with hugo
did not work for me, I always got not found
, because the site was trying to query the server on port 1313 (hugo) instead of 8080. Can you write down the instructions in a step-by-step manner?
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.
pushed a ./site/functions/README.md . PTAL
try { | ||
const resp = await fetch('/.netlify/functions/api/pluginCount'); | ||
const json = await resp.json(); | ||
count = json.data?.count ?? '<error>'; |
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.
It seems like optional chaining is relatively new (or at least those version numbers seem a little high). Do either of you think this should be changed to the old way or is it ok?
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 question immediately becomes: how far back do we want to be backwards compatible.
This case is trivial, but import
and async/await
is already more involved and the old style quickly becomes kind of ugly. We could warn page visitors by including a <script nomodule ..>
script to inform the user that the page is not working as intended and to switch to a newer browser.
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.
I think these are mostly fine. I tend to assume developers use latest browser versions most of the time. In that regard they're more savvy than average computer user.
Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
Oh no! I'm so slow. Apologies 🙇♂️ |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: ahmetb, 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 |
This relies on Netlify's serverless functions capability to render
plugin count (in the homepage) and plugin list (/docs/plugins) in
the website!
SIG Docs TL has cleared the usage of Functions feature here:
https://groups.google.com/g/kubernetes-sig-docs/c/cXqQm60tGs8
The serverless function is called "api" and it's accessible on path
/.netlify/functions/api/* and has two subpaths that return JSON responses:
This serverless function is built by placing a binary in /site/functions
named "api" and the build instruction is in 'netlify.toml' file.
Since we get plugin list by calling GitHub API, by default we run without
an authentication token. However after this is merged, I will go and add a
permissionless $GITHUB_ACCESS_TOKEN that I have created from my account.
Unauthenticated GitHub calls are limited to 60 per IP, but since I'll add a
personal access token, that should not be an issue. When debugging locally,
60 is also fine. (deploy previews for PRs from non-maintainers will not have
an access token available, so they will make unauthenticated calls to GH API.)
The responses from the dynamic functions are actually cached in Netlify's
CDN and that helps with the rate-limiting as well. I have currently set the
CDN cache duration to 1 hour for each response.
Please take a look at the previews and let me know if there is anything that
doesn't look good.
I might delete
generate-plugin-overview
tool and update the currentplugins.md in a followup PR.
/assign @chriskim06
/assign @corneliusweig