mirror of
https://github.com/komodorio/helm-dashboard.git
synced 2026-03-24 11:48:04 +00:00
Query ArtifactHub for repo suggestion (#225)
* Query ArtifactHub for repo suggestion * Refactor & improve * Add notice on local chart support
This commit is contained in:
5
main.go
5
main.go
@@ -34,6 +34,11 @@ type options struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
err := os.Setenv("HD_VERSION", version) // for anyone willing to access it
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to remember app version because of error: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
opts := parseFlags()
|
opts := parseFlags()
|
||||||
if opts.BindHost == "" {
|
if opts.BindHost == "" {
|
||||||
host := os.Getenv("HD_BIND")
|
host := os.Getenv("HD_BIND")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -207,7 +208,21 @@ func (h *HelmHandler) RepoLatestVer(c *gin.Context) {
|
|||||||
if len(res) > 0 {
|
if len(res) > 0 {
|
||||||
c.IndentedJSON(http.StatusOK, res[:1])
|
c.IndentedJSON(http.StatusOK, res[:1])
|
||||||
} else {
|
} else {
|
||||||
|
// caching it to avoid too many requests
|
||||||
|
found, err := h.Data.Cache.String("chart-artifacthub-query/"+qp.Name, nil, func() (string, error) {
|
||||||
|
return h.repoFromArtifactHub(qp.Name)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if found == "" {
|
||||||
c.Status(http.StatusNoContent)
|
c.Status(http.StatusNoContent)
|
||||||
|
} else {
|
||||||
|
c.Header("Content-Type", "application/json")
|
||||||
|
c.String(http.StatusOK, found)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -553,6 +568,49 @@ func (h *HelmHandler) handleGetSection(rel *objects.Release, section string, rDi
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *HelmHandler) repoFromArtifactHub(name string) (string, error) {
|
||||||
|
results, err := objects.QueryArtifactHub(name)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to query ArtifactHub: %s", err)
|
||||||
|
return "", nil // swallowing the error to not annoy users
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(results, func(i, j int) bool {
|
||||||
|
// we prefer official repos
|
||||||
|
if results[i].Repository.Official && !results[j].Repository.Official {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// or from verified publishers
|
||||||
|
if results[i].Repository.VerifiedPublisher && !results[j].Repository.VerifiedPublisher {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// or just more popular
|
||||||
|
return results[i].Stars > results[j].Stars
|
||||||
|
})
|
||||||
|
|
||||||
|
r := results[0]
|
||||||
|
buf, err := json.Marshal([]*RepoChartElement{{
|
||||||
|
Name: r.Name,
|
||||||
|
Version: r.Version,
|
||||||
|
AppVersion: r.AppVersion,
|
||||||
|
Description: r.Description,
|
||||||
|
Repository: r.Repository.Name,
|
||||||
|
URLs: []string{r.Repository.Url},
|
||||||
|
IsSuggestedRepo: true,
|
||||||
|
}})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
type RepoChartElement struct { // TODO: do we need it at all? there is existing repo.ChartVersion in Helm
|
type RepoChartElement struct { // TODO: do we need it at all? there is existing repo.ChartVersion in Helm
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
@@ -563,6 +621,7 @@ type RepoChartElement struct { // TODO: do we need it at all? there is existing
|
|||||||
InstalledName string `json:"installed_name"`
|
InstalledName string `json:"installed_name"`
|
||||||
Repository string `json:"repository"`
|
Repository string `json:"repository"`
|
||||||
URLs []string `json:"urls"`
|
URLs []string `json:"urls"`
|
||||||
|
IsSuggestedRepo bool `json:"isSuggestedRepo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func HReleaseToJSON(o *release.Release) *ReleaseElement {
|
func HReleaseToJSON(o *release.Release) *ReleaseElement {
|
||||||
|
|||||||
90
pkg/dashboard/objects/artifacthub.go
Normal file
90
pkg/dashboard/objects/artifacthub.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package objects
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
|
neturl "net/url"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mxArtifactHub sync.Mutex
|
||||||
|
|
||||||
|
func QueryArtifactHub(chartName string) ([]*ArtifactHubResult, error) {
|
||||||
|
mxArtifactHub.Lock() // to avoid parallel request spike
|
||||||
|
defer mxArtifactHub.Unlock()
|
||||||
|
|
||||||
|
url := os.Getenv("HD_ARTIFACT_HUB_URL")
|
||||||
|
if url == "" {
|
||||||
|
url = "https://artifacthub.io/api/v1/packages/search"
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := neturl.Parse(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.RawQuery = "offset=0&limit=5&facets=false&kind=0&deprecated=false&sort=relevance&ts_query_web=" + neturl.QueryEscape(chartName)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", p.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("User-Agent", "Komodor Helm Dashboard/"+os.Getenv("HD_VERSION")) // TODO
|
||||||
|
|
||||||
|
log.Debugf("Making HTTP request: %v", req)
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("failed to fetch %s : %s", p.String(), res.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ArtifactHubResults{}
|
||||||
|
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Packages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArtifactHubResults struct {
|
||||||
|
Packages []*ArtifactHubResult `json:"packages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArtifactHubResult struct {
|
||||||
|
PackageId string `json:"package_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
NormalizedName string `json:"normalized_name"`
|
||||||
|
LogoImageId string `json:"logo_image_id"`
|
||||||
|
Stars int `json:"stars"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
AppVersion string `json:"app_version"`
|
||||||
|
Deprecated bool `json:"deprecated"`
|
||||||
|
Signed bool `json:"signed"`
|
||||||
|
ProductionOrganizationsCount int `json:"production_organizations_count"`
|
||||||
|
Ts int `json:"ts"`
|
||||||
|
Repository ArtifactHubRepo `json:"repository"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArtifactHubRepo struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
Kind int `json:"kind"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Official bool `json:"official"`
|
||||||
|
DisplayName string `json:"display_name"`
|
||||||
|
RepositoryId string `json:"repository_id"`
|
||||||
|
ScannerDisabled bool `json:"scanner_disabled"`
|
||||||
|
OrganizationName string `json:"organization_name"`
|
||||||
|
VerifiedPublisher bool `json:"verified_publisher"`
|
||||||
|
OrganizationDisplayName string `json:"organization_display_name"`
|
||||||
|
}
|
||||||
@@ -25,7 +25,11 @@ function checkUpgradeable(name) {
|
|||||||
if (!data || !data.length) {
|
if (!data || !data.length) {
|
||||||
btnUpgradeCheck.prop("disabled", true)
|
btnUpgradeCheck.prop("disabled", true)
|
||||||
btnUpgradeCheck.text("")
|
btnUpgradeCheck.text("")
|
||||||
$("#btnAddRepository").text("Add repository for it")
|
$("#btnAddRepository").text("Add repository for it").data("suggestRepo", "")
|
||||||
|
} else if (data[0].isSuggestedRepo) {
|
||||||
|
btnUpgradeCheck.prop("disabled", true)
|
||||||
|
btnUpgradeCheck.text("")
|
||||||
|
$("#btnAddRepository").text("Add repository for it: "+data[0].repository).data("suggestRepo", data[0].repository).data("suggestRepoUrl", data[0].urls[0])
|
||||||
} else {
|
} else {
|
||||||
$("#btnAddRepository").text("")
|
$("#btnAddRepository").text("")
|
||||||
btnUpgradeCheck.text("Check for new version")
|
btnUpgradeCheck.text("Check for new version")
|
||||||
@@ -399,7 +403,12 @@ $("#btnRollback").click(function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
$("#btnAddRepository").click(function () {
|
$("#btnAddRepository").click(function () {
|
||||||
|
const self=$(this)
|
||||||
setHashParam("section", "repository")
|
setHashParam("section", "repository")
|
||||||
|
if (self.data("suggestRepo")) {
|
||||||
|
setHashParam("suggestRepo", self.data("suggestRepo"))
|
||||||
|
setHashParam("suggestRepoUrl", self.data("suggestRepoUrl"))
|
||||||
|
}
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,7 @@
|
|||||||
<button class="btn btn-sm border-secondary text-muted">
|
<button class="btn btn-sm border-secondary text-muted">
|
||||||
<i class="bi-plus-lg"></i> Add Repository
|
<i class="bi-plus-lg"></i> Add Repository
|
||||||
</button>
|
</button>
|
||||||
|
<div class="mt-2 p-2 small">Charts developers: you can also add local directories as chart source. Use <span class="font-monospace text-success">--local-chart</span> CLI switch to specify it.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-9 repo-details bg-white b-shadow pt-4 px-5 overflow-auto rounded">
|
<div class="col-9 repo-details bg-white b-shadow pt-4 px-5 overflow-auto rounded">
|
||||||
|
|||||||
@@ -89,7 +89,13 @@ function buildChartCard(elm) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isNewerVersion(elm.chartVersion, data[0].version)) {
|
if (isNewerVersion(elm.chartVersion, data[0].version)) {
|
||||||
card.find(".rel-name span").append("<span class='bi-arrow-up-circle-fill ms-2 text-success' title='Upgrade available: "+data[0].version+"'></span>")
|
const icon = $("<span class='ms-2 text-success' title='Upgrade available: " + data[0].version + " from " + data[0].repository + "'></span>")
|
||||||
|
if (data[0].isSuggestedRepo) {
|
||||||
|
icon.addClass("bi-arrow-up-circle")
|
||||||
|
} else {
|
||||||
|
icon.addClass("bi-arrow-up-circle-fill")
|
||||||
|
}
|
||||||
|
card.find(".rel-name span").append(icon)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,13 @@ function loadRepoView() {
|
|||||||
$("#sectionRepo .repo-details").hide()
|
$("#sectionRepo .repo-details").hide()
|
||||||
$("#sectionRepo").show()
|
$("#sectionRepo").show()
|
||||||
|
|
||||||
|
$("#repoAddModal input[name=name]").val(getHashParam("suggestRepo"))
|
||||||
|
$("#repoAddModal input[name=url]").val(getHashParam("suggestRepoUrl"))
|
||||||
|
|
||||||
|
if (getHashParam("suggestRepo")) {
|
||||||
|
$("#sectionRepo .repo-list .btn").click()
|
||||||
|
}
|
||||||
|
|
||||||
$.getJSON("/api/helm/repositories").fail(function (xhr) {
|
$.getJSON("/api/helm/repositories").fail(function (xhr) {
|
||||||
reportError("Failed to get list of repositories", xhr)
|
reportError("Failed to get list of repositories", xhr)
|
||||||
sendStats('Get repo', {'status': 'fail'});
|
sendStats('Get repo', {'status': 'fail'});
|
||||||
@@ -85,6 +92,8 @@ $("#inputSearch").keyup(function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
$("#sectionRepo .repo-list .btn").click(function () {
|
$("#sectionRepo .repo-list .btn").click(function () {
|
||||||
|
setHashParam("suggestRepo", null)
|
||||||
|
setHashParam("suggestRepoUrl", null)
|
||||||
const myModal = new bootstrap.Modal(document.getElementById('repoAddModal'), {});
|
const myModal = new bootstrap.Modal(document.getElementById('repoAddModal'), {});
|
||||||
myModal.show()
|
myModal.show()
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user