Skip to content

Commit 9b2252f

Browse files
feat: Added ability to rollback a helm release (#1359)
* IsReleaseInstalled API integration * GetInstalledAppVersion enabled for hyperion (moved from full to common) * added installedAppVersionId in app info * function rename * sending clusterId and EnvId in response * update helm release with chart linking * revert * revert * fix * Rollback release * some refactoring * bug fix * some refactoring * openapi.yaml and md files moved to spec
1 parent 670cc7e commit 9b2252f

20 files changed

+1309
-100
lines changed

api/appStore/deployment/AppStoreDeploymentRestHandler.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"fmt"
2525
client "github.com/devtron-labs/devtron/api/helm-app"
2626
openapi "github.com/devtron-labs/devtron/api/helm-app/openapiClient"
27+
openapi2 "github.com/devtron-labs/devtron/api/openapi/openapiClient"
2728
"github.com/devtron-labs/devtron/api/restHandler/common"
2829
"github.com/devtron-labs/devtron/internal/util"
2930
appStoreBean "github.com/devtron-labs/devtron/pkg/appStore/bean"
@@ -38,13 +39,15 @@ import (
3839
"net/http"
3940
"strconv"
4041
"strings"
42+
"time"
4143
)
4244

4345
type AppStoreDeploymentRestHandler interface {
4446
InstallApp(w http.ResponseWriter, r *http.Request)
4547
GetInstalledAppsByAppStoreId(w http.ResponseWriter, r *http.Request)
4648
DeleteInstalledApp(w http.ResponseWriter, r *http.Request)
4749
LinkHelmApplicationToChartStore(w http.ResponseWriter, r *http.Request)
50+
RollbackApplication(w http.ResponseWriter, r *http.Request)
4851
}
4952

5053
type AppStoreDeploymentRestHandlerImpl struct {
@@ -269,6 +272,7 @@ func (handler AppStoreDeploymentRestHandlerImpl) DeleteInstalledApp(w http.Respo
269272
}
270273

271274

275+
272276
func (handler *AppStoreDeploymentRestHandlerImpl) LinkHelmApplicationToChartStore(w http.ResponseWriter, r *http.Request) {
273277
request := &openapi.UpdateReleaseWithChartLinkingRequest{}
274278
decoder := json.NewDecoder(r.Body)
@@ -310,3 +314,59 @@ func (handler *AppStoreDeploymentRestHandlerImpl) LinkHelmApplicationToChartStor
310314

311315
common.WriteJsonResp(w, err, res, http.StatusOK)
312316
}
317+
318+
func (handler *AppStoreDeploymentRestHandlerImpl) RollbackApplication(w http.ResponseWriter, r *http.Request) {
319+
request := &openapi2.RollbackReleaseRequest{}
320+
decoder := json.NewDecoder(r.Body)
321+
err := decoder.Decode(request)
322+
if err != nil {
323+
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
324+
return
325+
}
326+
327+
userId, err := handler.userAuthService.GetLoggedInUser(r)
328+
if userId == 0 || err != nil {
329+
common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized)
330+
return
331+
}
332+
333+
// get installed app which can not be null
334+
installedApp, err := handler.appStoreDeploymentService.GetInstalledApp(int(request.GetInstalledAppId()))
335+
if err != nil {
336+
handler.Logger.Errorw("Error in getting installed app", "err", err)
337+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
338+
return
339+
}
340+
if installedApp == nil {
341+
handler.Logger.Errorw("Installed App can not be null", "request", request)
342+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
343+
return
344+
}
345+
346+
//rbac block starts from here
347+
var rbacObject string
348+
token := r.Header.Get("token")
349+
if installedApp.AppOfferingMode == util2.SERVER_MODE_HYPERION {
350+
rbacObject = handler.enforcerUtilHelm.GetHelmObjectByClusterId(installedApp.ClusterId, installedApp.Namespace, installedApp.AppName)
351+
} else {
352+
rbacObject = handler.enforcerUtil.GetHelmObjectByAppNameAndEnvId(installedApp.AppName, installedApp.EnvironmentId)
353+
}
354+
if ok := handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionUpdate, rbacObject); !ok {
355+
common.WriteJsonResp(w, fmt.Errorf("unauthorized user"), nil, http.StatusForbidden)
356+
return
357+
}
358+
//rbac block ends here
359+
360+
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
361+
defer cancel()
362+
success, err := handler.appStoreDeploymentService.RollbackApplication(ctx, request, installedApp, userId)
363+
if err != nil {
364+
handler.Logger.Errorw("Error in Rollback release", "err", err, "payload", request)
365+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
366+
return
367+
}
368+
res := &openapi2.RollbackReleaseResponse {
369+
Success: &success,
370+
}
371+
common.WriteJsonResp(w, err, res, http.StatusOK)
372+
}

api/appStore/deployment/AppStoreDeploymentRouter.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,7 @@ func (router AppStoreDeploymentRouterImpl) Init(configRouter *mux.Router) {
4848

4949
configRouter.Path("/application/helm/link-to-chart-store").
5050
HandlerFunc(router.appStoreDeploymentRestHandler.LinkHelmApplicationToChartStore).Methods("PUT")
51+
52+
configRouter.Path("/application/rollback").
53+
HandlerFunc(router.appStoreDeploymentRestHandler.RollbackApplication).Methods("PUT")
5154
}

api/helm-app/HelmAppRestHandler.go

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"errors"
77
openapi "github.com/devtron-labs/devtron/api/helm-app/openapiClient"
8+
openapi2 "github.com/devtron-labs/devtron/api/openapi/openapiClient"
89
"github.com/devtron-labs/devtron/api/restHandler/common"
910
appStoreBean "github.com/devtron-labs/devtron/pkg/appStore/bean"
1011
appStoreDeploymentCommon "github.com/devtron-labs/devtron/pkg/appStore/deployment/common"
@@ -17,6 +18,7 @@ import (
1718
"net/http"
1819
"strconv"
1920
"strings"
21+
"time"
2022
)
2123

2224
type HelmAppRestHandler interface {
@@ -30,6 +32,7 @@ type HelmAppRestHandler interface {
3032
DeleteApplication(w http.ResponseWriter, r *http.Request)
3133
UpdateApplication(w http.ResponseWriter, r *http.Request)
3234
GetDeploymentDetail(w http.ResponseWriter, r *http.Request)
35+
RollbackApplication(w http.ResponseWriter, r *http.Request)
3336
}
3437

3538
type HelmAppRestHandlerImpl struct {
@@ -187,12 +190,23 @@ func (handler *HelmAppRestHandlerImpl) GetDeploymentHistory(w http.ResponseWrite
187190
return
188191
}
189192
//RBAC enforcer Ends
190-
res, err := handler.helmAppService.GetDeploymentHistory(context.Background(), appIdentifier)
193+
194+
deploymentHistory, err := handler.helmAppService.GetDeploymentHistory(context.Background(), appIdentifier)
195+
if err != nil {
196+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
197+
return
198+
}
199+
200+
installedApp, err := handler.appStoreDeploymentCommonService.GetInstalledAppByClusterNamespaceAndName(appIdentifier.ClusterId, appIdentifier.Namespace, appIdentifier.ReleaseName)
191201
if err != nil {
192202
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
193203
return
194204
}
195205

206+
res := &DeploymentHistoryAndInstalledAppInfo{
207+
DeploymentHistory: deploymentHistory.GetDeploymentHistory(),
208+
InstalledAppInfo: convertToInstalledAppInfo(installedApp),
209+
}
196210
common.WriteJsonResp(w, err, res, http.StatusOK)
197211
}
198212

@@ -404,6 +418,42 @@ func (handler *HelmAppRestHandlerImpl) GetDeploymentDetail(w http.ResponseWriter
404418
common.WriteJsonResp(w, err, res, http.StatusOK)
405419
}
406420

421+
func (handler *HelmAppRestHandlerImpl) RollbackApplication(w http.ResponseWriter, r *http.Request) {
422+
request := &openapi2.RollbackReleaseRequest{}
423+
decoder := json.NewDecoder(r.Body)
424+
err := decoder.Decode(request)
425+
if err != nil {
426+
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
427+
return
428+
}
429+
appIdentifier, err := handler.helmAppService.DecodeAppId(request.GetHAppId())
430+
if err != nil {
431+
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
432+
return
433+
}
434+
// RBAC enforcer applying
435+
rbacObject := handler.enforcerUtil.GetHelmObjectByClusterId(appIdentifier.ClusterId, appIdentifier.Namespace, appIdentifier.ReleaseName)
436+
token := r.Header.Get("token")
437+
if ok := handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionUpdate, rbacObject); !ok {
438+
common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden)
439+
return
440+
}
441+
//RBAC enforcer Ends
442+
443+
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
444+
defer cancel()
445+
success, err := handler.helmAppService.RollbackRelease(ctx, appIdentifier, request.GetVersion())
446+
if err != nil {
447+
handler.logger.Errorw("error in rollback helm release", "err", err)
448+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
449+
return
450+
}
451+
res := &openapi2.RollbackReleaseResponse{
452+
Success: &success,
453+
}
454+
common.WriteJsonResp(w, err, res, http.StatusOK)
455+
}
456+
407457
func (handler *HelmAppRestHandlerImpl) checkHelmAuth(token string, object string) bool {
408458
if ok := handler.enforcer.Enforce(token, casbin.ResourceHelmApp, casbin.ActionGet, strings.ToLower(object)); !ok {
409459
return false
@@ -438,6 +488,11 @@ type ReleaseAndInstalledAppInfo struct {
438488
ReleaseInfo *ReleaseInfo `json:"releaseInfo"`
439489
}
440490

491+
type DeploymentHistoryAndInstalledAppInfo struct {
492+
InstalledAppInfo *InstalledAppInfo `json:"installedAppInfo"`
493+
DeploymentHistory []*HelmAppDeploymentDetail `json:"deploymentHistory"`
494+
}
495+
441496
type InstalledAppInfo struct {
442497
AppId int `json:"appId"`
443498
InstalledAppId int `json:"installedAppId"`

api/helm-app/HelmAppRouter.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,6 @@ func (impl *HelmAppRouterImpl) InitAppListRouter(helmRouter *mux.Router) {
3838

3939
helmRouter.Path("/deployment-detail").Queries("appId", "{appId}").Queries("version", "{version}").
4040
HandlerFunc(impl.helmAppRestHandler.GetDeploymentDetail).Methods("GET")
41+
42+
helmRouter.Path("/rollback").HandlerFunc(impl.helmAppRestHandler.RollbackApplication).Methods("PUT")
4143
}

api/helm-app/HelmAppService.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type HelmAppService interface {
3232
InstallRelease(ctx context.Context, clusterId int, installReleaseRequest *InstallReleaseRequest) (*InstallReleaseResponse, error)
3333
UpdateApplicationWithChartInfo(ctx context.Context, clusterId int, updateReleaseRequest *InstallReleaseRequest) (*openapi.UpdateReleaseResponse, error)
3434
IsReleaseInstalled(ctx context.Context, app *AppIdentifier) (bool, error)
35+
RollbackRelease(ctx context.Context, app *AppIdentifier, version int32) (bool, error)
3536
}
3637

3738
type HelmAppServiceImpl struct {
@@ -378,7 +379,6 @@ func (impl *HelmAppServiceImpl) UpdateApplicationWithChartInfo(ctx context.Conte
378379
return response, nil
379380
}
380381

381-
382382
func (impl *HelmAppServiceImpl) IsReleaseInstalled(ctx context.Context, app *AppIdentifier) (bool, error) {
383383
config, err := impl.getClusterConf(app.ClusterId)
384384
if err != nil {
@@ -401,6 +401,30 @@ func (impl *HelmAppServiceImpl) IsReleaseInstalled(ctx context.Context, app *App
401401
return apiResponse.Result, nil
402402
}
403403

404+
func (impl *HelmAppServiceImpl) RollbackRelease(ctx context.Context, app *AppIdentifier, version int32) (bool, error) {
405+
config, err := impl.getClusterConf(app.ClusterId)
406+
if err != nil {
407+
impl.logger.Errorw("error in fetching cluster detail", "clusterId", app.ClusterId, "err", err)
408+
return false, err
409+
}
410+
411+
req := &RollbackReleaseRequest{
412+
ReleaseIdentifier: &ReleaseIdentifier{
413+
ClusterConfig: config,
414+
ReleaseName: app.ReleaseName,
415+
ReleaseNamespace: app.Namespace,
416+
},
417+
Version: version,
418+
}
419+
420+
apiResponse, err := impl.helmAppClient.RollbackRelease(ctx, req)
421+
if err != nil {
422+
impl.logger.Errorw("error in rollback release", "err", err)
423+
return false, err
424+
}
425+
426+
return apiResponse.Result, nil
427+
}
404428

405429
type AppIdentifier struct {
406430
ClusterId int `json:"clusterId"`

api/helm-app/applicationClient.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type HelmAppClient interface {
2323
InstallRelease(ctx context.Context, in *InstallReleaseRequest) (*InstallReleaseResponse, error)
2424
UpdateApplicationWithChartInfo(ctx context.Context, in *InstallReleaseRequest) (*UpgradeReleaseResponse, error)
2525
IsReleaseInstalled(ctx context.Context, in *ReleaseIdentifier) (*BooleanResponse, error)
26+
RollbackRelease(ctx context.Context, in *RollbackReleaseRequest) (*BooleanResponse, error)
2627
}
2728

2829
type HelmAppClientImpl struct {
@@ -235,3 +236,15 @@ func (impl *HelmAppClientImpl) IsReleaseInstalled(ctx context.Context, in *Relea
235236
}
236237
return response, nil
237238
}
239+
240+
func (impl *HelmAppClientImpl) RollbackRelease(ctx context.Context, in *RollbackReleaseRequest) (*BooleanResponse, error) {
241+
applicationClient, err := impl.getApplicationClient()
242+
if err != nil {
243+
return nil, err
244+
}
245+
response, err := applicationClient.RollbackRelease(ctx, in)
246+
if err != nil {
247+
return nil, err
248+
}
249+
return response, nil
250+
}

0 commit comments

Comments
 (0)