Skip to content

Commit

Permalink
Query ArtifactHub for repo suggestion (#225)
Browse files Browse the repository at this point in the history
* Query ArtifactHub for repo suggestion

* Refactor & improve

* Add notice on local chart support
  • Loading branch information
undera committed Mar 2, 2023
1 parent 679d31e commit bbb425b
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 3 deletions.
5 changes: 5 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ type options struct {
}

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()
if opts.BindHost == "" {
host := os.Getenv("HD_BIND")
Expand Down
61 changes: 60 additions & 1 deletion pkg/dashboard/handlers/helmHandlers.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handlers

import (
"encoding/json"
"errors"
"fmt"
"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -207,7 +208,21 @@ func (h *HelmHandler) RepoLatestVer(c *gin.Context) {
if len(res) > 0 {
c.IndentedJSON(http.StatusOK, res[:1])
} else {
c.Status(http.StatusNoContent)
// 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)
} else {
c.Header("Content-Type", "application/json")
c.String(http.StatusOK, found)
}
}
}

Expand Down Expand Up @@ -553,6 +568,49 @@ func (h *HelmHandler) handleGetSection(rel *objects.Release, section string, rDi
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
Name string `json:"name"`
Version string `json:"version"`
Expand All @@ -563,6 +621,7 @@ type RepoChartElement struct { // TODO: do we need it at all? there is existing
InstalledName string `json:"installed_name"`
Repository string `json:"repository"`
URLs []string `json:"urls"`
IsSuggestedRepo bool `json:"isSuggestedRepo"`
}

func HReleaseToJSON(o *release.Release) *ReleaseElement {
Expand Down
90 changes: 90 additions & 0 deletions pkg/dashboard/objects/artifacthub.go
Original file line number Diff line number Diff line change
@@ -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"`
}
11 changes: 10 additions & 1 deletion pkg/dashboard/static/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ function checkUpgradeable(name) {
if (!data || !data.length) {
btnUpgradeCheck.prop("disabled", true)
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 {
$("#btnAddRepository").text("")
btnUpgradeCheck.text("Check for new version")
Expand Down Expand Up @@ -399,7 +403,12 @@ $("#btnRollback").click(function () {
})

$("#btnAddRepository").click(function () {
const self=$(this)
setHashParam("section", "repository")
if (self.data("suggestRepo")) {
setHashParam("suggestRepo", self.data("suggestRepo"))
setHashParam("suggestRepoUrl", self.data("suggestRepoUrl"))
}
window.location.reload()
})

Expand Down
1 change: 1 addition & 0 deletions pkg/dashboard/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ <h4 class="fs-6">Repositories</h4>
<button class="btn btn-sm border-secondary text-muted">
<i class="bi-plus-lg"></i> Add Repository
</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 class="col-9 repo-details bg-white b-shadow pt-4 px-5 overflow-auto rounded">
Expand Down
8 changes: 7 additions & 1 deletion pkg/dashboard/static/list-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,13 @@ function buildChartCard(elm) {
}

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)
}
})

Expand Down
9 changes: 9 additions & 0 deletions pkg/dashboard/static/repo.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ function loadRepoView() {
$("#sectionRepo .repo-details").hide()
$("#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) {
reportError("Failed to get list of repositories", xhr)
sendStats('Get repo', {'status': 'fail'});
Expand Down Expand Up @@ -85,6 +92,8 @@ $("#inputSearch").keyup(function () {
})

$("#sectionRepo .repo-list .btn").click(function () {
setHashParam("suggestRepo", null)
setHashParam("suggestRepoUrl", null)
const myModal = new bootstrap.Modal(document.getElementById('repoAddModal'), {});
myModal.show()
})
Expand Down

0 comments on commit bbb425b

Please sign in to comment.