Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions api/restHandler/app/BuildPipelineRestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type DevtronAppBuildRestHandler interface {
GetExternalCi(w http.ResponseWriter, r *http.Request)
GetExternalCiById(w http.ResponseWriter, r *http.Request)
PatchCiPipelines(w http.ResponseWriter, r *http.Request)
PatchCiMaterialSourceWithAppIdAndEnvironmentId(w http.ResponseWriter, r *http.Request)
TriggerCiPipeline(w http.ResponseWriter, r *http.Request)
GetCiPipelineMin(w http.ResponseWriter, r *http.Request)
GetCIPipelineById(w http.ResponseWriter, r *http.Request)
Expand Down Expand Up @@ -230,6 +231,72 @@ func (handler PipelineConfigRestHandlerImpl) UpdateBranchCiPipelinesWithRegex(w
common.WriteJsonResp(w, err, resp, http.StatusOK)
}

func (handler PipelineConfigRestHandlerImpl) parseSourceChangeRequest(w http.ResponseWriter, r *http.Request) (*bean.CiMaterialPatchRequest, int32, error) {
decoder := json.NewDecoder(r.Body)
userId, err := handler.userAuthService.GetLoggedInUser(r)
if userId == 0 || err != nil {
common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized)
return nil, 0, err
}
var patchRequest bean.CiMaterialPatchRequest
err = decoder.Decode(&patchRequest)

if err != nil {
handler.Logger.Errorw("request err, PatchCiPipeline", "err", err, "PatchCiPipeline", patchRequest)
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
return nil, 0, err
}
return &patchRequest, userId, nil
}

func (handler PipelineConfigRestHandlerImpl) authorizeCiSourceChangeRequest(w http.ResponseWriter, patchRequest *bean.CiMaterialPatchRequest, token string) error {
handler.Logger.Debugw("update request ", "req", patchRequest)
app, err := handler.pipelineBuilder.GetApp(patchRequest.AppId)
if err != nil {
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
return err
}
if app.AppType != helper.CustomApp {
err = fmt.Errorf("only custom apps supported")
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
return err
}
resourceName := handler.enforcerUtil.GetAppRBACName(app.AppName)
if ok := handler.enforcer.Enforce(token, casbin.ResourceApplications, casbin.ActionUpdate, resourceName); !ok {
err = fmt.Errorf("unauthorized user")
common.WriteJsonResp(w, err, "Unauthorized User", http.StatusForbidden)
return err
}
err = handler.validator.Struct(patchRequest)
if err != nil {
handler.Logger.Errorw("validation err", "err", err)
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
return err
}
return nil
}

func (handler PipelineConfigRestHandlerImpl) PatchCiMaterialSourceWithAppIdAndEnvironmentId(w http.ResponseWriter, r *http.Request) {
patchRequest, userId, err := handler.parseSourceChangeRequest(w, r)
if err != nil {
handler.Logger.Errorw("Parse error, PatchCiMaterialSource", "err", err, "PatchCiMaterialSource", patchRequest)
return
}
token := r.Header.Get("token")
if err = handler.authorizeCiSourceChangeRequest(w, patchRequest, token); err != nil {
handler.Logger.Errorw("Authorization error, PatchCiMaterialSource", "err", err, "PatchCiMaterialSource", patchRequest)
return
}

createResp, err := handler.pipelineBuilder.PatchCiMaterialSource(patchRequest, userId)
if err != nil {
handler.Logger.Errorw("service err, PatchCiPipelines", "err", err, "PatchCiPipelines", patchRequest)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
return
}
common.WriteJsonResp(w, err, createResp, http.StatusOK)
}

func (handler PipelineConfigRestHandlerImpl) PatchCiPipelines(w http.ResponseWriter, r *http.Request) {
decoder := json.NewDecoder(r.Body)
userId, err := handler.userAuthService.GetLoggedInUser(r)
Expand Down
214 changes: 214 additions & 0 deletions api/restHandler/app/BuildPipelineRestHandler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package app

import (
"bytes"
"fmt"
"github.com/devtron-labs/devtron/internal/sql/repository/helper"
"github.com/devtron-labs/devtron/internal/util"
"github.com/devtron-labs/devtron/pkg/bean"
"github.com/devtron-labs/devtron/pkg/pipeline/mock_pipeline"
mock_user "github.com/devtron-labs/devtron/pkg/user/mocks"
mock_casbin "github.com/devtron-labs/devtron/pkg/user/mocks/casbin"
mocks_rbac "github.com/devtron-labs/devtron/util/mocks/rbac"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"gopkg.in/go-playground/validator.v9"
"net/http"
"net/http/httptest"
"testing"
)

func TestPipelineConfigRestHandlerImpl_PatchCiMaterialSource(t *testing.T) {
logger, err := util.NewSugardLogger()
if err != nil {
panic(err)
}
type fields struct {
userAuthService *mock_user.MockUserService
pipelineBuilder *mock_pipeline.MockPipelineBuilder
validator *validator.Validate
enforcer *mock_casbin.MockEnforcer
enforcerUtil *mocks_rbac.MockEnforcerUtil
}
type args struct {
w http.ResponseWriter
r *http.Request
}
tests := []struct {
name string
fields fields
args args
body string
setup func(fields2 *fields)
expectedStatusCode int
}{
{
name: "when user is not found, it should return unauthorized",
fields: fields{
validator: validator.New(),
},
body: "{\"appId\":2, \"environmentId\": 1 ,\"source\":{\"type\":\"SOURCE_TYPE_BRANCH_FIXED\",\"value\":\"main10\",\"regex\":\"\"}}",
setup: func(fields2 *fields) {
ctrl := gomock.NewController(t)
fields2.pipelineBuilder = mock_pipeline.NewMockPipelineBuilder(ctrl)
fields2.enforcer = mock_casbin.NewMockEnforcer(ctrl)
fields2.enforcerUtil = mocks_rbac.NewMockEnforcerUtil(ctrl)
fields2.userAuthService = mock_user.NewMockUserService(ctrl)
fields2.userAuthService.EXPECT().GetLoggedInUser(gomock.Any()).Return(int32(0), fmt.Errorf("user not found")).Times(1)
},
expectedStatusCode: http.StatusUnauthorized,
},
{
name: "when request is malformed, it should return bad request",
fields: fields{
validator: validator.New(),
},
body: "{\"appId\":4, \"id\": 5 ,-\"ciMaterial\":[{\"gitMaterialId\":4,\"id\":5,\"source\":{\"type\":\"SOURCE_TYPE_BRANCH_FIXED\",\"value\":\"main3\",\"regex\":\"\"}}]}",
setup: func(fields2 *fields) {
ctrl := gomock.NewController(t)
fields2.pipelineBuilder = mock_pipeline.NewMockPipelineBuilder(ctrl)
fields2.enforcer = mock_casbin.NewMockEnforcer(ctrl)
fields2.enforcerUtil = mocks_rbac.NewMockEnforcerUtil(ctrl)
fields2.userAuthService = mock_user.NewMockUserService(ctrl)
fields2.userAuthService.EXPECT().GetLoggedInUser(gomock.Any()).Return(int32(1), nil).Times(1)
},
expectedStatusCode: http.StatusBadRequest,
},
{
name: "when app is not found for the given appId, it should return bad request",
fields: fields{
validator: validator.New(),
},
body: "{\"appId\":4, \"id\": 5 ,\"ciMaterial\":[{\"gitMaterialId\":4,\"id\":5,\"source\":{\"type\":\"SOURCE_TYPE_BRANCH_FIXED\",\"value\":\"main3\",\"regex\":\"\"}}]}",
setup: func(fields2 *fields) {
ctrl := gomock.NewController(t)
fields2.pipelineBuilder = mock_pipeline.NewMockPipelineBuilder(ctrl)
fields2.pipelineBuilder.EXPECT().GetApp(4).Return(nil, fmt.Errorf("app not found")).Times(1)
fields2.enforcer = mock_casbin.NewMockEnforcer(ctrl)
fields2.enforcerUtil = mocks_rbac.NewMockEnforcerUtil(ctrl)
fields2.userAuthService = mock_user.NewMockUserService(ctrl)
fields2.userAuthService.EXPECT().GetLoggedInUser(gomock.Any()).Return(int32(1), nil).Times(1)
//fields2.userAuthService.EXPECT().IsSuperAdmin(1).Return(true, nil).Times(1)
},
expectedStatusCode: http.StatusBadRequest,
},
{
name: "when validator fails, it should return Bad request",
fields: fields{
validator: validator.New(),
},
body: "{\"appId\":4, \"id\": 5 ,\"ciMaterial\":[{\"gitMaterialId\":4,\"id\":5,\"source\":{\"type\":\"SOURCE_TYPE_BRANCH_FIXED\",\"value\":\"main3\",\"regex\":\"\"}}]}",
setup: func(fields2 *fields) {
ctrl := gomock.NewController(t)
_ = fields2.validator.RegisterValidation("name-component", func(fl validator.FieldLevel) bool {
return false
})
fields2.pipelineBuilder = mock_pipeline.NewMockPipelineBuilder(ctrl)
fields2.pipelineBuilder.EXPECT().GetApp(4).Return(&bean.CreateAppDTO{AppName: "Super App", Id: 4, AppType: helper.Job}, nil).Times(1)
fields2.enforcer = mock_casbin.NewMockEnforcer(ctrl)
fields2.enforcerUtil = mocks_rbac.NewMockEnforcerUtil(ctrl)
//fields2.enforcerUtil.EXPECT().GetAppRBACName("Super App")
fields2.userAuthService = mock_user.NewMockUserService(ctrl)
fields2.userAuthService.EXPECT().GetLoggedInUser(gomock.Any()).Return(int32(1), nil).Times(1)
//fields2.userAuthService.EXPECT().IsSuperAdmin(1).Return(true, nil).Times(1)

},
expectedStatusCode: http.StatusBadRequest,
},
{
name: "when app is not jobtype and enforce fails, it should return forbidden",
fields: fields{
validator: validator.New(),
},
body: "{\"appId\":4, \"id\": 5 ,\"ciMaterial\":[{\"gitMaterialId\":4,\"id\":5,\"source\":{\"type\":\"SOURCE_TYPE_BRANCH_FIXED\",\"value\":\"main3\",\"regex\":\"\"}}]}",
setup: func(fields2 *fields) {
ctrl := gomock.NewController(t)
fields2.pipelineBuilder = mock_pipeline.NewMockPipelineBuilder(ctrl)
fields2.pipelineBuilder.EXPECT().GetApp(4).Return(&bean.CreateAppDTO{AppName: "Super App", Id: 4, AppType: helper.CustomApp}, nil).Times(1)
fields2.enforcer = mock_casbin.NewMockEnforcer(ctrl)
fields2.enforcer.EXPECT().Enforce(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(false).Times(1)

fields2.enforcerUtil = mocks_rbac.NewMockEnforcerUtil(ctrl)
fields2.enforcerUtil.EXPECT().GetAppRBACName("Super App")
fields2.userAuthService = mock_user.NewMockUserService(ctrl)
fields2.userAuthService.EXPECT().GetLoggedInUser(gomock.Any()).Return(int32(1), nil).Times(1)
//fields2.userAuthService.EXPECT().IsSuperAdmin(1).Return(false, nil).Times(1)

},
expectedStatusCode: http.StatusForbidden,
},
{
name: "when PatchCiMaterialSource call fails, it should return internal server error",
fields: fields{
validator: validator.New(),
},
body: "{\"appId\":4, \"environmentId\": 1 ,\"source\":{\"type\":\"SOURCE_TYPE_BRANCH_FIXED\",\"value\":\"main10\",\"regex\":\"\"}}",
setup: func(fields2 *fields) {
_ = fields2.validator.RegisterValidation("name-component", func(fl validator.FieldLevel) bool {
return true
})
ctrl := gomock.NewController(t)
fields2.pipelineBuilder = mock_pipeline.NewMockPipelineBuilder(ctrl)
fields2.pipelineBuilder.EXPECT().GetApp(4).Return(&bean.CreateAppDTO{AppName: "Super App", Id: 4, AppType: helper.CustomApp}, nil).Times(1)
fields2.pipelineBuilder.EXPECT().PatchCiMaterialSource(gomock.Any(), int32(1)).Return(nil, fmt.Errorf("failed to patch"))
fields2.enforcer = mock_casbin.NewMockEnforcer(ctrl)
fields2.enforcer.EXPECT().Enforce(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(true).Times(1)
fields2.enforcerUtil = mocks_rbac.NewMockEnforcerUtil(ctrl)
fields2.enforcerUtil.EXPECT().GetAppRBACName("Super App")
fields2.userAuthService = mock_user.NewMockUserService(ctrl)
fields2.userAuthService.EXPECT().GetLoggedInUser(gomock.Any()).Return(int32(1), nil).Times(1)
//fields2.userAuthService.EXPECT().IsSuperAdmin(1).Return(false, nil).Times(1)

},
expectedStatusCode: http.StatusInternalServerError,
},
{
name: "when PatchCiMaterialSource call passes, it should return statusok",
fields: fields{
validator: validator.New(),
},
body: "{\"appId\":4, \"environmentId\": 1 ,\"source\":{\"type\":\"SOURCE_TYPE_BRANCH_FIXED\",\"value\":\"main10\",\"regex\":\"\"}}",
setup: func(fields2 *fields) {
_ = fields2.validator.RegisterValidation("name-component", func(fl validator.FieldLevel) bool {
return true
})
ctrl := gomock.NewController(t)
fields2.pipelineBuilder = mock_pipeline.NewMockPipelineBuilder(ctrl)
fields2.pipelineBuilder.EXPECT().GetApp(4).Return(&bean.CreateAppDTO{AppName: "Super App", Id: 4, AppType: helper.CustomApp}, nil).Times(1)
fields2.pipelineBuilder.EXPECT().PatchCiMaterialSource(gomock.Any(), int32(1)).Return(&bean.CiMaterialPatchRequest{AppId: 2}, nil)
fields2.enforcer = mock_casbin.NewMockEnforcer(ctrl)
fields2.enforcer.EXPECT().Enforce(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(true).Times(1)
fields2.enforcerUtil = mocks_rbac.NewMockEnforcerUtil(ctrl)
fields2.enforcerUtil.EXPECT().GetAppRBACName("Super App")
fields2.userAuthService = mock_user.NewMockUserService(ctrl)
fields2.userAuthService.EXPECT().GetLoggedInUser(gomock.Any()).Return(int32(1), nil).Times(1)
//fields2.userAuthService.EXPECT().IsSuperAdmin(1).Return(false, nil).Times(1)

},
expectedStatusCode: http.StatusOK,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.setup(&tt.fields)
handler := PipelineConfigRestHandlerImpl{
userAuthService: tt.fields.userAuthService,
pipelineBuilder: tt.fields.pipelineBuilder,
validator: tt.fields.validator,
enforcer: tt.fields.enforcer,
enforcerUtil: tt.fields.enforcerUtil,
Logger: logger,
}

req, err := http.NewRequest("PATCH", "/orchestrator/app/ci-pipeline/patch-branch", bytes.NewBuffer([]byte(tt.body)))
if err != nil {
t.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
h := http.HandlerFunc(handler.PatchCiMaterialSourceWithAppIdAndEnvironmentId)
h.ServeHTTP(rr, req)
assert.Equal(t, rr.Code, tt.expectedStatusCode)
})
}
}
1 change: 1 addition & 0 deletions api/router/PipelineConfigRouter.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func (router PipelineConfigRouterImpl) initPipelineConfigRouter(configRouter *mu
configRouter.Path("/external-ci/{appId}/{externalCiId}").HandlerFunc(router.restHandler.GetExternalCiById).Methods("GET")
configRouter.Path("/ci-pipeline/template/patch").HandlerFunc(router.restHandler.UpdateCiTemplate).Methods("POST")
configRouter.Path("/ci-pipeline/patch").HandlerFunc(router.restHandler.PatchCiPipelines).Methods("POST")
configRouter.Path("/ci-pipeline/patch-source").HandlerFunc(router.restHandler.PatchCiMaterialSourceWithAppIdAndEnvironmentId).Methods("PATCH")
configRouter.Path("/ci-pipeline/patch/regex").HandlerFunc(router.restHandler.UpdateBranchCiPipelinesWithRegex).Methods("POST")

configRouter.Path("/cd-pipeline/{cd_pipeline_id}/material").HandlerFunc(router.restHandler.GetArtifactsByCDPipeline).Methods("GET")
Expand Down
Loading