-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Currently, only a subset of available helm flags is implemented. Adding new flags should be trivial.
- Loading branch information
1 parent
ee343a0
commit e6f5259
Showing
33 changed files
with
979 additions
and
1,246 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package install | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"time" | ||
|
||
"helm.sh/helm/v3/pkg/release" | ||
|
||
"github.com/gojekfarm/albatross/pkg/helmcli/flags" | ||
"github.com/gojekfarm/albatross/pkg/logger" | ||
) | ||
|
||
type Request struct { | ||
Name string | ||
Chart string | ||
Values map[string]interface{} | ||
Flags Flags | ||
} | ||
|
||
type Flags struct { | ||
DryRun bool `json:"dry_run"` | ||
Version string | ||
flags.GlobalFlags | ||
} | ||
|
||
type Release struct { | ||
Name string `json:"name"` | ||
Namespace string `json:"namespace"` | ||
Version int `json:"version"` | ||
Updated time.Time `json:"updated_at,omitempty"` | ||
Status release.Status `json:"status"` | ||
Chart string `json:"chart"` | ||
AppVersion string `json:"app_version"` | ||
} | ||
|
||
type Response struct { | ||
Error string `json:"error,omitempty"` | ||
Status string `json:"status,omitempty"` | ||
Data string `json:"data,omitempty"` | ||
Release `json:"-"` | ||
} | ||
|
||
func Handler(service service) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
defer r.Body.Close() | ||
|
||
var req Request | ||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { | ||
logger.Errorf("[Install] error decoding request: %v", err) | ||
w.WriteHeader(http.StatusBadRequest) | ||
return | ||
} | ||
|
||
resp, err := service.Install(r.Context(), req) | ||
if err != nil { | ||
respondInstallError(w, "error while installing chart: %v", err) | ||
return | ||
} | ||
|
||
if err := json.NewEncoder(w).Encode(&resp); err != nil { | ||
respondInstallError(w, "error writing response: %v", err) | ||
return | ||
} | ||
}) | ||
} | ||
|
||
// TODO: This does not handle different status codes. | ||
func respondInstallError(w http.ResponseWriter, logprefix string, err error) { | ||
response := Response{Error: err.Error()} | ||
w.WriteHeader(http.StatusInternalServerError) | ||
if err := json.NewEncoder(w).Encode(&response); err != nil { | ||
logger.Errorf("[Install] %s %v", logprefix, err) | ||
w.WriteHeader(http.StatusInternalServerError) | ||
return | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package install | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"net/http/httptest" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/mock" | ||
"github.com/stretchr/testify/require" | ||
"github.com/stretchr/testify/suite" | ||
|
||
"github.com/gojekfarm/albatross/pkg/logger" | ||
|
||
"helm.sh/helm/v3/pkg/release" | ||
) | ||
|
||
type mockService struct { | ||
mock.Mock | ||
} | ||
|
||
func (s *mockService) Install(ctx context.Context, req Request) (Response, error) { | ||
args := s.Called(ctx, req) | ||
return args.Get(0).(Response), args.Error(1) | ||
} | ||
|
||
type InstallerTestSuite struct { | ||
suite.Suite | ||
recorder *httptest.ResponseRecorder | ||
server *httptest.Server | ||
mockService *mockService | ||
} | ||
|
||
func (s *InstallerTestSuite) SetupSuite() { | ||
logger.Setup("default") | ||
} | ||
|
||
func (s *InstallerTestSuite) SetupTest() { | ||
s.recorder = httptest.NewRecorder() | ||
s.mockService = new(mockService) | ||
handler := Handler(s.mockService) | ||
s.server = httptest.NewServer(handler) | ||
} | ||
|
||
func (s *InstallerTestSuite) TestShouldReturnDeployedStatusOnSuccessfulInstall() { | ||
chartName := "stable/redis-ha" | ||
body := fmt.Sprintf(`{"chart":"%s", "name": "redis-v5", "values": {"replicas": 2}, "flags": {"namespace": "albatross"}}`, chartName) | ||
|
||
req, _ := http.NewRequest("POST", fmt.Sprintf("%s/install", s.server.URL), strings.NewReader(body)) | ||
response := Response{ | ||
Status: release.StatusDeployed.String(), | ||
} | ||
|
||
s.mockService.On("Install", mock.Anything, mock.AnythingOfType("Request")).Return(response, nil) | ||
|
||
resp, err := http.DefaultClient.Do(req) | ||
assert.Equal(s.T(), http.StatusOK, resp.StatusCode) | ||
expectedResponse := `{"status":"deployed"}` + "\n" | ||
respBody, _ := ioutil.ReadAll(resp.Body) | ||
assert.Equal(s.T(), expectedResponse, string(respBody)) | ||
require.NoError(s.T(), err) | ||
s.mockService.AssertExpectations(s.T()) | ||
} | ||
|
||
func (s *InstallerTestSuite) TestShouldReturnInternalServerErrorOnFailure() { | ||
chartName := "stable/redis-ha" | ||
body := fmt.Sprintf(`{"chart":"%s", "name": "redis-v5"}`, chartName) | ||
|
||
req, _ := http.NewRequest("POST", fmt.Sprintf("%s/install", s.server.URL), strings.NewReader(body)) | ||
s.mockService.On("Install", mock.Anything, mock.AnythingOfType("Request")).Return(Response{}, errors.New("Invalid chart")) | ||
|
||
resp, err := http.DefaultClient.Do(req) | ||
|
||
assert.Equal(s.T(), http.StatusInternalServerError, resp.StatusCode) | ||
expectedResponse := `{"error":"Invalid chart"}` + "\n" | ||
respBody, _ := ioutil.ReadAll(resp.Body) | ||
assert.Equal(s.T(), expectedResponse, string(respBody)) | ||
require.NoError(s.T(), err) | ||
s.mockService.AssertExpectations(s.T()) | ||
} | ||
|
||
func (s *InstallerTestSuite) TestReturnShouldBadRequestOnInvalidRequest() { | ||
chartName := "stable/redis-ha" | ||
body := fmt.Sprintf(`{"chart":"%s", "name": "redis-v5}`, chartName) | ||
|
||
req, _ := http.NewRequest("POST", fmt.Sprintf("%s/install", s.server.URL), strings.NewReader(body)) | ||
s.mockService.On("Install", mock.Anything, mock.AnythingOfType("Request")).Return(Release{}, nil) | ||
|
||
resp, err := http.DefaultClient.Do(req) | ||
assert.Equal(s.T(), http.StatusBadRequest, resp.StatusCode) | ||
require.NoError(s.T(), err) | ||
s.mockService.AssertNotCalled(s.T(), "Install") | ||
} | ||
|
||
func (s *InstallerTestSuite) TearDownTest() { | ||
s.server.Close() | ||
} | ||
|
||
func TestInstallAPI(t *testing.T) { | ||
suite.Run(t, new(InstallerTestSuite)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package install | ||
|
||
import ( | ||
"context" | ||
|
||
"helm.sh/helm/v3/pkg/release" | ||
|
||
"github.com/gojekfarm/albatross/pkg/helmcli" | ||
"github.com/gojekfarm/albatross/pkg/helmcli/flags" | ||
) | ||
|
||
// TODO: Move the service interface to a common place for all apis | ||
type service interface { | ||
Install(ctx context.Context, req Request) (Response, error) | ||
} | ||
|
||
type Service struct{} | ||
|
||
func (s Service) Install(ctx context.Context, req Request) (Response, error) { | ||
installflags := flags.InstallFlags{ | ||
DryRun: req.Flags.DryRun, | ||
Version: req.Flags.Version, | ||
GlobalFlags: req.Flags.GlobalFlags, | ||
} | ||
icli := helmcli.NewInstaller(installflags) | ||
release, err := icli.Install(ctx, req.Name, req.Chart, req.Values) | ||
if err != nil { | ||
return Response{}, err | ||
} | ||
resp := Response{Status: release.Info.Status.String(), Release: releaseInfo(release)} | ||
if req.Flags.DryRun { | ||
resp.Data = release.Manifest | ||
} | ||
return resp, nil | ||
} | ||
|
||
func releaseInfo(release *release.Release) Release { | ||
return Release{ | ||
Name: release.Name, | ||
Namespace: release.Namespace, | ||
Version: release.Version, | ||
Updated: release.Info.FirstDeployed.Local().Time, | ||
Status: release.Info.Status, | ||
Chart: release.Chart.ChartFullPath(), | ||
AppVersion: release.Chart.AppVersion(), | ||
} | ||
} |
Oops, something went wrong.