diff --git a/cmd/service/service.go b/cmd/service/service.go index 46f146888..e4687c13b 100644 --- a/cmd/service/service.go +++ b/cmd/service/service.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/gorilla/mux" "net/http" "helm.sh/helm/v3/pkg/action" @@ -24,9 +25,8 @@ func setContentType(next http.Handler) http.Handler { } func startServer() { - router := http.NewServeMux() + router := mux.NewRouter() - //TODO: use gorilla mux and add middleware to write content type and other headers app := servercontext.App() logger.Setup("debug") @@ -42,10 +42,10 @@ func startServer() { api.NewUpgrader(actionUpgrade), api.NewHistory(actionHistory)) - router.Handle("/ping", setContentType(ping.Handler())) - router.Handle("/list", setContentType(api.List(service))) - router.Handle("/install", setContentType(api.Install(service))) - router.Handle("/upgrade", setContentType(api.Upgrade(service))) + router.Handle("/ping", setContentType(ping.Handler())).Methods(http.MethodGet) + router.Handle("/list", setContentType(api.List(service))).Methods(http.MethodPost) + router.Handle("/install", setContentType(api.Install(service))).Methods(http.MethodPost) + router.Handle("/upgrade", setContentType(api.Upgrade(service))).Methods(http.MethodPost) err := http.ListenAndServe(fmt.Sprintf(":%d", 8080), router) if err != nil { diff --git a/go.mod b/go.mod index c4d4c0214..2ebd22da5 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect github.com/google/go-cmp v0.3.1 // indirect github.com/googleapis/gnostic v0.3.1 // indirect + github.com/gorilla/mux v1.7.4 github.com/gosuri/uitable v0.0.1 github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f // indirect github.com/hashicorp/golang-lru v0.5.3 // indirect diff --git a/go.sum b/go.sum index 9a00ad5b6..1bf26ec0b 100644 --- a/go.sum +++ b/go.sum @@ -286,6 +286,8 @@ github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZs github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= diff --git a/pkg/action/list.go b/pkg/action/list.go index 5be60ac42..5e931e68a 100644 --- a/pkg/action/list.go +++ b/pkg/action/list.go @@ -17,6 +17,7 @@ limitations under the License. package action import ( + "fmt" "path" "regexp" @@ -154,6 +155,8 @@ func (l *List) Run() ([]*release.Release, error) { results, err := l.cfg.Releases.List(func(rel *release.Release) bool { // Skip anything that the mask doesn't cover currentStatus := l.StateMask.FromName(rel.Info.Status.String()) + fmt.Println("state mask", l.StateMask) + fmt.Println("current status", currentStatus) if l.StateMask¤tStatus == 0 { return false } diff --git a/pkg/api/list.go b/pkg/api/list.go index 2255e97de..3c15ba7d4 100644 --- a/pkg/api/list.go +++ b/pkg/api/list.go @@ -2,6 +2,7 @@ package api import ( "encoding/json" + "io" "net/http" "helm.sh/helm/v3/pkg/api/logger" @@ -33,32 +34,27 @@ func List(svc Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var response ListResponse var request ListRequest - decoder := json.NewDecoder(r.Body) - if err := decoder.Decode(&request); err != nil { - logger.Errorf("[List] error decoding request: %v", err) - response.Error = err.Error() - payload, _ := json.Marshal(response) + if err := json.NewDecoder(r.Body).Decode(&request); err == io.EOF || err != nil { + logger.Errorf("[List] error decoding request: %v", err.Error()) w.WriteHeader(http.StatusBadRequest) - w.Write(payload) + response.Error = err.Error() + json.NewEncoder(w).Encode(response) return } defer r.Body.Close() - helmReleases, err := svc.List(request.ReleaseStatus) + helmReleases, err := svc.List(request.ReleaseStatus, request.NameSpace) if err != nil { respondInstallError(w, "error while listing charts: %v", err) - return } response = ListResponse{"", helmReleases} - payload, err := json.Marshal(response) + err = json.NewEncoder(w).Encode(response) if err != nil { respondInstallError(w, "error writing response: %v", err) return } - - w.Write(payload) }) } diff --git a/pkg/api/list_api_test.go b/pkg/api/list_api_test.go index e0ff6a70d..44fbb72a8 100644 --- a/pkg/api/list_api_test.go +++ b/pkg/api/list_api_test.go @@ -1,8 +1,8 @@ package api_test import ( + "encoding/json" "fmt" - "io/ioutil" "net/http" "net/http/httptest" "strings" @@ -42,8 +42,8 @@ func (m *mockList) SetStateMask() { m.Called() } -func (m *mockList) SetState(state action.ListStates) { - m.Called(state) +func (m *mockList) SetConfig(state action.ListStates, allNameSpaces bool) { + m.Called(state, allNameSpaces) } func (s *ListTestSuite) SetupSuite() { @@ -68,43 +68,53 @@ func (s *ListTestSuite) TestShouldReturnReleasesWhenSuccessfulAPICall() { timeFromStr, _ := time.Parse(layout, str) body := `{"release_status":"deployed"}` req, _ := http.NewRequest("POST", fmt.Sprintf("%s/list", s.server.URL), strings.NewReader(body)) - releases := []*release.Release{ - { - Name: "test-release", - Namespace: "test-namespace", - Info: &release.Info{Status: release.StatusDeployed, LastDeployed: timeFromStr}, - Version: 1, - Chart: &chart.Chart{Metadata: &chart.Metadata{Name: "test-release", Version: "0.1", AppVersion: "0.1"}}, - }, - } + + releases := []*release.Release{{Name: "test-release", + Namespace: "test-namespace", + Info: &release.Info{Status: release.StatusDeployed, LastDeployed: timeFromStr}, + Version: 1, + Chart: &chart.Chart{Metadata: &chart.Metadata{Name: "test-release", Version: "0.1", AppVersion: "0.1"}}, + }} + s.mockList.On("SetStateMask") - s.mockList.On("SetState", action.ListDeployed) + s.mockList.On("SetConfig", action.ListDeployed, true) s.mockList.On("Run").Return(releases, nil) - resp, httpErr := http.DefaultClient.Do(req) + res, err := http.DefaultClient.Do(req) + assert.Equal(s.T(), 200, res.StatusCode) - assert.Equal(s.T(), 200, resp.StatusCode) + var actualResponse api.ListResponse + err = json.NewDecoder(res.Body).Decode(&actualResponse) - expectedResponse := `{"releases":[{"name":"test-release","namespace":"test-namespace","revision":1,"updated_at":"2014-11-12T11:45:26.371Z","status":"deployed","chart":"test-release-0.1","app_version":"0.1"}]}` - respBody, err := ioutil.ReadAll(resp.Body) + expectedResponse := api.ListResponse{Error: "", + Releases: []api.Release{{"test-release", + "test-namespace", + 1, + timeFromStr, + release.StatusDeployed, + "test-release-0.1", + "0.1", + }}} - assert.Equal(s.T(), expectedResponse, string(respBody)) - require.NoError(s.T(), httpErr) + assert.Equal(s.T(), expectedResponse.Releases[0], actualResponse.Releases[0]) require.NoError(s.T(), err) s.mockList.AssertExpectations(s.T()) } func (s *ListTestSuite) TestShouldReturnBadRequestErrorIfItHasInvalidCharacter() { - body := `{"request_id":"test-request-id""""}` + body := `{"release_status":"unknown""""}` req, _ := http.NewRequest("POST", fmt.Sprintf("%s/list", s.server.URL), strings.NewReader(body)) - resp, err := http.DefaultClient.Do(req) + res, err := http.DefaultClient.Do(req) + + assert.Equal(s.T(), 400, res.StatusCode) + + expectedResponse := "invalid character '\"' after object key:value pair" - assert.Equal(s.T(), 400, resp.StatusCode) + var actualResponse api.ListResponse + err = json.NewDecoder(res.Body).Decode(&actualResponse) - expectedResponse := `{"error":"invalid character '\"' after object key:value pair"}` - respBody, _ := ioutil.ReadAll(resp.Body) - assert.Equal(s.T(), expectedResponse, string(respBody)) + assert.Equal(s.T(), expectedResponse, actualResponse.Error) require.NoError(s.T(), err) } diff --git a/pkg/api/lister.go b/pkg/api/lister.go index 8d7fcec52..29e5dc110 100644 --- a/pkg/api/lister.go +++ b/pkg/api/lister.go @@ -12,13 +12,14 @@ type Lister struct { type ListRunner interface { Run() ([]*release.Release, error) SetStateMask() - SetState(state action.ListStates) + SetConfig(state action.ListStates, allNameSpaces bool) } func NewList(action *action.List) *Lister { return &Lister{action} } -func (l *Lister) SetState(state action.ListStates) { +func (l *Lister) SetConfig(state action.ListStates, allNameSpaces bool) { l.StateMask = state + l.AllNamespaces = allNameSpaces } diff --git a/pkg/api/service.go b/pkg/api/service.go index c074a0ef0..2522c7fef 100644 --- a/pkg/api/service.go +++ b/pkg/api/service.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "strings" "helm.sh/helm/v3/pkg/action" @@ -140,7 +139,7 @@ func (s Service) validate(icfg ReleaseConfig, values ChartValues) error { return nil } -func (s Service) List(releaseStatus string) ([]Release, error) { +func (s Service) List(releaseStatus string, namespace string) ([]Release, error) { listStates := new(action.ListStates) state := action.ListAll @@ -152,8 +151,14 @@ func (s Service) List(releaseStatus string) ([]Release, error) { return nil, errors.New("invalid release status") } - s.ListRunner.SetState(state) + allNameSpaces := true + if namespace != "" { + allNameSpaces = false + } + + s.ListRunner.SetConfig(state, allNameSpaces) s.ListRunner.SetStateMask() + releases, err := s.ListRunner.Run() if err != nil { return nil, err diff --git a/pkg/api/service_test.go b/pkg/api/service_test.go index d3fba77f9..da10d499a 100644 --- a/pkg/api/service_test.go +++ b/pkg/api/service_test.go @@ -277,11 +277,11 @@ func (s *ServiceTestSuite) TestUpgradeShouldReturnErrorOnInvalidChart() { func (s *ServiceTestSuite) TestListShouldReturnErrorOnFailureOfListRun() { var releases []*release.Release releaseStatus := "deployed" - s.lister.On("SetState", action.ListDeployed) + s.lister.On("SetConfig", action.ListDeployed, false) s.lister.On("SetStateMask") s.lister.On("Run").Return(releases, errors.New("cluster issue")) - res, err := s.svc.List(releaseStatus) + res, err := s.svc.List(releaseStatus, "test-namespace") t := s.T() assert.Error(t, err, "cluster issue") @@ -293,51 +293,41 @@ func (s *ServiceTestSuite) TestListShouldReturnAllReleasesIfNoFilterIsPassed() { layout := "2006-01-02T15:04:05.000Z" str := "2014-11-12T11:45:26.371Z" releaseStatus := "" - var releases []*release.Release timeFromStr, _ := time.Parse(layout, str) - releases = append(releases, - &release.Release{Name: "test-release", - Namespace: "test-namespace", - Info: &release.Info{Status: release.StatusDeployed, LastDeployed: timeFromStr}, - Version: 1, - Chart: &chart.Chart{Metadata: &chart.Metadata{Name: "test-release", Version: "0.1", AppVersion: "0.1"}}, - }) - s.lister.On("SetState", action.ListAll) - s.lister.On("SetStateMask") + releases := []*release.Release{{Name: "test-release", + Namespace: "test-namespace", + Info: &release.Info{Status: release.StatusDeployed, LastDeployed: timeFromStr}, + Version: 1, + Chart: &chart.Chart{Metadata: &chart.Metadata{Name: "test-release", Version: "0.1", AppVersion: "0.1"}}, + }} + + s.lister.On("SetConfig", action.ListAll, false) + s.lister.On("SetStateMask") s.lister.On("Run").Return(releases, nil) - res, err := s.svc.List(releaseStatus) + res, err := s.svc.List(releaseStatus, "test-namespace") t := s.T() assert.NoError(t, err) require.NotNil(t, res) - var response []api.Release - response = append(response, api.Release{Name: "test-release", - Namespace: "test-namespace", - Revision: 1, - Updated: timeFromStr, - Status: release.StatusDeployed, - Chart: "test-release-0.1", - AppVersion: "0.1", - }) - - assert.Equal(t, 1, len(res)) - assert.Equal(t, "test-release", response[0].Name) - assert.Equal(t, "test-namespace", response[0].Namespace) - assert.Equal(t, 1, response[0].Revision) - assert.Equal(t, timeFromStr, response[0].Updated) - assert.Equal(t, release.StatusDeployed, response[0].Status) - assert.Equal(t, "test-release-0.1", response[0].Chart) - assert.Equal(t, "0.1", response[0].AppVersion) - assert.Equal(t, response, releases[0]) + response := []api.Release{{"test-release", + "test-namespace", + 1, + timeFromStr, + release.StatusDeployed, + "test-release-0.1", + "0.1", + }} + + assert.Equal(t, response, res) s.lister.AssertExpectations(t) } func (s *ServiceTestSuite) TestListShouldReturnErrorIfInvalidStatusIsPassedAsFilter() { releaseStatus := "invalid" - _, err := s.svc.List(releaseStatus) + _, err := s.svc.List(releaseStatus, "test-namespace") t := s.T() assert.EqualError(t, err, "invalid release status") @@ -346,11 +336,11 @@ func (s *ServiceTestSuite) TestListShouldReturnErrorIfInvalidStatusIsPassedAsFil func (s *ServiceTestSuite) TestListShouldReturnDeployedReleasesIfDeployedIsPassedAsFilter() { var releases []*release.Release releaseStatus := "deployed" - s.lister.On("SetState", action.ListDeployed) + s.lister.On("SetConfig", action.ListDeployed, false) s.lister.On("SetStateMask") s.lister.On("Run").Return(releases, nil) - _, err := s.svc.List(releaseStatus) + _, err := s.svc.List(releaseStatus, "test-namespace") t := s.T() assert.NoError(t, err)