Skip to content
This repository has been archived by the owner on Jul 16, 2021. It is now read-only.

Commit

Permalink
refresh single chart draft (#447)
Browse files Browse the repository at this point in the history
* refresh single chart draft

* extra package repohelper + tests

* remove trailing line

* more coverage

* more coverage
  • Loading branch information
samifruit514 authored and prydonius committed May 14, 2018
1 parent 0b2f36f commit c95282b
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 29 deletions.
81 changes: 54 additions & 27 deletions src/api/data/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,19 @@ package cache

import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
"strings"
"sync"

log "github.com/Sirupsen/logrus"

"github.com/kubernetes-helm/monocular/src/api/data"
"github.com/kubernetes-helm/monocular/src/api/data/cache/charthelper"
"github.com/kubernetes-helm/monocular/src/api/data/cache/repohelper"
"github.com/kubernetes-helm/monocular/src/api/data/helpers"
"github.com/kubernetes-helm/monocular/src/api/datastore"
"github.com/kubernetes-helm/monocular/src/api/models"
swaggermodels "github.com/kubernetes-helm/monocular/src/api/swagger/models"
"github.com/kubernetes-helm/monocular/src/api/swagger/restapi/operations/charts"
"github.com/kubernetes-helm/monocular/src/api/version"
)

type cachedCharts struct {
Expand Down Expand Up @@ -133,6 +129,58 @@ func (c *cachedCharts) Search(params charts.SearchChartsParams) ([]*swaggermodel
return ret, nil
}

// Refresh is the interface implementation for data.Charts
// It refreshes cached data for a specific repo and chart
func (c *cachedCharts) RefreshChart(repoName string, chartName string) error {

log.WithFields(log.Fields{
"path": charthelper.DataDirBase(),
}).Info("Using cache directory")

db, closer := c.dbSession.DB()
defer closer()

repo, err := models.GetRepo(db, repoName)
if err != nil {
return err
}
charts, err := repohelper.GetChartsFromRepoIndexFile(repo)
if err != nil {
return err
}

didUpdate := false
for _, chart := range charts {
if *chart.Name == chartName {
didUpdate = true
ch := make(chan chanItem, len(charts))
defer close(ch)
go processChartMetadata(chart, repo.URL, ch)

it := <-ch
if it.err == nil {
c.rwm.Lock()
// find the key
for k, chart := range c.allCharts[repo.Name] {
if chart.Name == it.chart.Name && chart.Version == it.chart.Version {
c.allCharts[repo.Name][k] = it.chart
}
}
c.rwm.Unlock()
} else {
return it.err
}

}
}

if didUpdate == false {
return fmt.Errorf("no chart \"%s\" found for repo %s\n", chartName, repo.Name)
} else {
return nil
}
}

// Refresh is the interface implementation for data.Charts
// It refreshes cached data for all authoritative repository+chart data
func (c *cachedCharts) Refresh() error {
Expand All @@ -151,28 +199,7 @@ func (c *cachedCharts) Refresh() error {
return err
}
for _, repo := range repos {
u, _ := url.Parse(repo.URL)
u.Path = path.Join(u.Path, "index.yaml")

// 1 - Download repo index
var client http.Client
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return err
}
req.Header.Set("User-Agent", version.GetUserAgent())
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}

// 2 - Parse repo index
charts, err := helpers.ParseYAMLRepo(body, repo.Name)
charts, err := repohelper.GetChartsFromRepoIndexFile(repo)
if err != nil {
return err
}
Expand Down
50 changes: 50 additions & 0 deletions src/api/data/cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/arschles/assert"
"github.com/kubernetes-helm/monocular/src/api/data"
"github.com/kubernetes-helm/monocular/src/api/data/cache/charthelper"
"github.com/kubernetes-helm/monocular/src/api/data/cache/repohelper"
"github.com/kubernetes-helm/monocular/src/api/data/helpers"
"github.com/kubernetes-helm/monocular/src/api/datastore"
"github.com/kubernetes-helm/monocular/src/api/models"
Expand Down Expand Up @@ -92,6 +93,55 @@ func TestCachedChartsRefresh(t *testing.T) {
assert.NoErr(t, err)
}

func TestCachedChartsRefreshChart(t *testing.T) {
// Stubs Download and processing
DownloadAndExtractChartTarballOrig := charthelper.DownloadAndExtractChartTarball
defer func() { charthelper.DownloadAndExtractChartTarball = DownloadAndExtractChartTarballOrig }()
charthelper.DownloadAndExtractChartTarball = func(chart *swaggermodels.ChartPackage, repoURL string) error { return nil }

DownloadAndProcessChartIconOrig := charthelper.DownloadAndProcessChartIcon
defer func() { charthelper.DownloadAndProcessChartIcon = DownloadAndProcessChartIconOrig }()
charthelper.DownloadAndProcessChartIcon = func(chart *swaggermodels.ChartPackage) error { return nil }

err := chartsImplementation.RefreshChart("stable", "datadog")
assert.NoErr(t, err)
}

func TestCachedChartsRefreshChartRepoNotFound(t *testing.T) {
// Stubs Download and processing
DownloadAndExtractChartTarballOrig := charthelper.DownloadAndExtractChartTarball
defer func() { charthelper.DownloadAndExtractChartTarball = DownloadAndExtractChartTarballOrig }()
charthelper.DownloadAndExtractChartTarball = func(chart *swaggermodels.ChartPackage, repoURL string) error { return nil }

DownloadAndProcessChartIconOrig := charthelper.DownloadAndProcessChartIcon
defer func() { charthelper.DownloadAndProcessChartIcon = DownloadAndProcessChartIconOrig }()
charthelper.DownloadAndProcessChartIcon = func(chart *swaggermodels.ChartPackage) error { return nil }

err := chartsImplementation.RefreshChart("stable", "inexistant chart")
assert.Err(t, err, errors.New("no chart \"inexistant chart\" found for repo stable\n"))
}

func TestCachedChartsRefreshChartIndexFileDownloadError(t *testing.T) {
// Stubs Download and processing
DownloadAndExtractChartTarballOrig := charthelper.DownloadAndExtractChartTarball
defer func() { charthelper.DownloadAndExtractChartTarball = DownloadAndExtractChartTarballOrig }()
charthelper.DownloadAndExtractChartTarball = func(chart *swaggermodels.ChartPackage, repoURL string) error { return nil }

DownloadAndProcessChartIconOrig := charthelper.DownloadAndProcessChartIcon
defer func() { charthelper.DownloadAndProcessChartIcon = DownloadAndProcessChartIconOrig }()
charthelper.DownloadAndProcessChartIcon = func(chart *swaggermodels.ChartPackage) error { return nil }

GetChartsFromRepoIndexFileOrig := repohelper.GetChartsFromRepoIndexFile
defer func() { repohelper.GetChartsFromRepoIndexFile = GetChartsFromRepoIndexFileOrig }()
repohelper.GetChartsFromRepoIndexFile = func(repo *models.Repo) ([]*swaggermodels.ChartPackage, error) {
return nil, errors.New("Error downloading repo index file")
}

err := chartsImplementation.RefreshChart("stable", "zookeper")
assert.Err(t, err, errors.New("Error downloading repo index file"))
//repohelper.GetChartsFromRepoIndexFile = backupFunc
}

func TestCachedChartsRefreshErrorPropagation(t *testing.T) {
tests := []struct {
name string
Expand Down
44 changes: 44 additions & 0 deletions src/api/data/cache/repohelper/repo_helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package repohelper

import (
"io/ioutil"
"net/http"
"net/url"
"path"

"github.com/kubernetes-helm/monocular/src/api/data/helpers"
"github.com/kubernetes-helm/monocular/src/api/models"
swaggermodels "github.com/kubernetes-helm/monocular/src/api/swagger/models"
"github.com/kubernetes-helm/monocular/src/api/version"
)

// GetRepoIndexFile Get the charts from the index file
var GetChartsFromRepoIndexFile = func(repo *models.Repo) ([]*swaggermodels.ChartPackage, error) {
u, _ := url.Parse(repo.URL)
u.Path = path.Join(u.Path, "index.yaml")

// 1 - Download repo index
var client http.Client
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", version.GetUserAgent())
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

// 2 - Parse repo index
charts, err := helpers.ParseYAMLRepo(body, repo.Name)
if err != nil {
return nil, err
}

return charts, nil
}
1 change: 1 addition & 0 deletions src/api/data/charts.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ type Charts interface {
Search(params charts.SearchChartsParams) ([]*models.ChartPackage, error)
// Refresh freshens charts data
Refresh() error
RefreshChart(Repo string, ChartName string) error
}
24 changes: 24 additions & 0 deletions src/api/handlers/charts/charts.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,30 @@ func (c *ChartHandlers) GetChart(w http.ResponseWriter, req *http.Request, param
renderer.Render.JSON(w, http.StatusOK, payload)
}

// RefreshChart is the handler for the /charts/{repo}/{name} endpoint
func (c *ChartHandlers) RefreshChart(w http.ResponseWriter, req *http.Request, params handlers.Params) {
chartPackage, err := c.chartsImplementation.ChartFromRepo(params["repo"], params["chartName"])
if err != nil {
log.Printf("data.chartsapi.ChartFromRepo(%s, %s) error (%s)", params["repo"], params["chartName"], err)
notFound(w, ChartResourceName)
return
}
db, closer := c.dbSession.DB()
defer closer()
chartResource := helpers.MakeChartResource(db, chartPackage)
err = c.chartsImplementation.RefreshChart(params["repo"], params["chartName"])

if err != nil {
message := fmt.Sprintf("data.chartsapi.RefreshChart(%s, %s) error (%s)", params["repo"], params["chartName"], err)
log.Printf(message)
renderer.Render.JSON(w, http.StatusBadRequest, models.Error{Code: pointerto.Int64(http.StatusBadRequest), Message: &message})
return

}
payload := handlers.DataResourceBody(chartResource)
renderer.Render.JSON(w, http.StatusOK, payload)
}

// GetChartVersion is the handler for the /charts/{repo}/{name}/versions/{version} endpoint
func (c *ChartHandlers) GetChartVersion(w http.ResponseWriter, req *http.Request, params handlers.Params) {
chartPackage, err := c.chartsImplementation.ChartVersionFromRepo(params["repo"], params["chartName"], params["version"])
Expand Down
31 changes: 31 additions & 0 deletions src/api/handlers/charts/charts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,37 @@ func TestGetChartsInRepo404(t *testing.T) {
testutil.AssertErrBodyData(t, http.StatusNotFound, ChartResourceName+"s", httpBody)
}

func TestRefreshChart200(t *testing.T) {
req, err := http.NewRequest("POST", "/v1/charts/"+testutil.RepoName+"/"+testutil.ChartName+"/refresh", nil)
assert.NoErr(t, err)
res := httptest.NewRecorder()
chartHandlers.RefreshChart(res, req, handlers.Params{"repo": testutil.RepoName, "chartName": testutil.ChartName})
assert.Equal(t, res.Code, http.StatusOK, "expect a 200 response code")
}

func TestRefreshChart404(t *testing.T) {
req, err := http.NewRequest("POST", "/v1/charts/"+testutil.RepoName+"/"+testutil.ChartName+"/refresh", nil)
assert.NoErr(t, err)
res := httptest.NewRecorder()
chartHandlers.RefreshChart(res, req, handlers.Params{"repo": testutil.RepoName, "chartName": "inexistant chart"})
assert.Equal(t, res.Code, http.StatusNotFound, "expect a 404 response code")
}

func TestRefreshChart400(t *testing.T) {
req, err := http.NewRequest("POST", "/v1/charts/"+testutil.RepoName+"/"+testutil.ChartName+"/refresh", nil)
assert.NoErr(t, err)
res := httptest.NewRecorder()

chImplementation := mocks.NewMockCharts(mocks.MockedMethods{
RefreshChart: func(repoName string, chartName string) error {
return errors.New("error refreshing chart")
},
})

NewChartHandlers(dbSession, chImplementation).RefreshChart(res, req, handlers.Params{"repo": testutil.RepoName, "chartName": testutil.ChartName})
assert.Equal(t, res.Code, http.StatusBadRequest, "expect a 400 response code")
}

func TestChartHTTPBody(t *testing.T) {
w := httptest.NewRecorder()
chart, err := chartsImplementation.ChartFromRepo(testutil.RepoName, testutil.ChartName)
Expand Down
1 change: 1 addition & 0 deletions src/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func setupRoutes(conf config.Configuration, chartsImplementation data.Charts, he
apiv1.Methods("GET").Path("/charts/search").HandlerFunc(chartHandlers.SearchCharts)
apiv1.Methods("GET").Path("/charts/{repo}").Handler(handlers.WithParams(chartHandlers.GetChartsInRepo))
apiv1.Methods("GET").Path("/charts/{repo}/{chartName}").Handler(handlers.WithParams(chartHandlers.GetChart))
apiv1.Methods("POST").Path("/charts/{repo}/{chartName}/refresh").Handler(handlers.WithParams(chartHandlers.RefreshChart))

// Chart Version routes
apiv1.Methods("GET").Path("/charts/{repo}/{chartName}/versions").Handler(handlers.WithParams(chartHandlers.GetChartVersions))
Expand Down
11 changes: 9 additions & 2 deletions src/api/mocks/charts.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import (

// MockedMethods contains pointers to mocked implementations of methods
type MockedMethods struct {
All func() ([]*models.ChartPackage, error)
Search func(params chartsapi.SearchChartsParams) ([]*models.ChartPackage, error)
All func() ([]*models.ChartPackage, error)
Search func(params chartsapi.SearchChartsParams) ([]*models.ChartPackage, error)
RefreshChart func(repoName string, chartName string) error
}

// mockCharts fulfills the data.Charts interface
Expand Down Expand Up @@ -145,6 +146,12 @@ func (c *mockCharts) Search(params chartsapi.SearchChartsParams) ([]*models.Char
func (c *mockCharts) Refresh() error {
return nil
}
func (c *mockCharts) RefreshChart(repoName string, chartName string) error {
if c.mockedMethods.RefreshChart != nil {
return c.mockedMethods.RefreshChart(repoName, chartName)
}
return nil
}

// getMockRepo is a convenience that loads a yaml repo from the filesystem
func getMockRepo(repo string) ([]byte, error) {
Expand Down
10 changes: 10 additions & 0 deletions src/api/mocks/charts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,13 @@ func TestMockChartsAllFromRepo(t *testing.T) {
assert.ExistsErr(t, err, "sent bogus repo name to GetChartsInRepo")
assert.True(t, len(noCharts) == 0, "empty charts slice")
}

func TestMockChartsRefresh(t *testing.T) {
err := chartsImplementation.Refresh()
assert.NoErr(t, err)
}

func TestMockChartsRefreshChart(t *testing.T) {
err := chartsImplementation.RefreshChart(testutil.RepoName, testutil.ChartName)
assert.NoErr(t, err)
}

0 comments on commit c95282b

Please sign in to comment.