Skip to content

Commit

Permalink
enhancement: enforcing deployment type in environment (#3616)
Browse files Browse the repository at this point in the history
* deployment type allowed config fixes

* wip: app clone fix

* fixing test

* pr review comments
  • Loading branch information
iamayushm authored Jul 12, 2023
1 parent e9223a6 commit bd37887
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 119 deletions.
32 changes: 32 additions & 0 deletions api/restHandler/AttributesRestHandlder.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type AttributesRestHandler interface {
GetAttributesById(w http.ResponseWriter, r *http.Request)
GetAttributesActiveList(w http.ResponseWriter, r *http.Request)
GetAttributesByKey(w http.ResponseWriter, r *http.Request)
AddDeploymentEnforcementConfig(w http.ResponseWriter, r *http.Request)
}

type AttributesRestHandlerImpl struct {
Expand Down Expand Up @@ -191,3 +192,34 @@ func (handler AttributesRestHandlerImpl) GetAttributesByKey(w http.ResponseWrite
}
common.WriteJsonResp(w, nil, res, http.StatusOK)
}

func (handler AttributesRestHandlerImpl) AddDeploymentEnforcementConfig(w http.ResponseWriter, r *http.Request) {
userId, err := handler.userService.GetLoggedInUser(r)
if userId == 0 || err != nil {
common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized)
return
}
decoder := json.NewDecoder(r.Body)
var dto attributes.AttributesDto
err = decoder.Decode(&dto)
if err != nil {
handler.logger.Errorw("request err, AddAttributes", "err", err, "payload", dto)
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
return
}

token := r.Header.Get("token")
if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionCreate, "*"); !ok {
common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden)
return
}

handler.logger.Infow("request payload, AddAttributes", "payload", dto)
resp, err := handler.attributesService.AddDeploymentEnforcementConfig(&dto)
if err != nil {
handler.logger.Errorw("service err, AddAttributes", "err", err, "payload", dto)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
return
}
common.WriteJsonResp(w, nil, resp, http.StatusOK)
}
2 changes: 2 additions & 0 deletions api/router/AttributesRouter.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@ func (router AttributesRouterImpl) InitAttributesRouter(attributesRouter *mux.Ro
HandlerFunc(router.attributesRestHandler.GetAttributesById).Methods("GET")
attributesRouter.Path("/active/list").
HandlerFunc(router.attributesRestHandler.GetAttributesActiveList).Methods("GET")
attributesRouter.Path("/create/deploymentConfig").
HandlerFunc(router.attributesRestHandler.AddDeploymentEnforcementConfig).Methods("POST")
}
27 changes: 26 additions & 1 deletion pkg/appClone/AppCloneService.go
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,31 @@ func (impl *AppCloneServiceImpl) CreateCdPipeline(req *cloneCdPipelineRequest, c
if strings.HasPrefix(pipelineName, req.refAppName) {
pipelineName = strings.Replace(pipelineName, req.refAppName+"-", "", 1)
}
// by default all deployment types are allowed
AllowedDeploymentAppTypes := map[string]bool{
util.PIPELINE_DEPLOYMENT_TYPE_ACD: true,
util.PIPELINE_DEPLOYMENT_TYPE_HELM: true,
}
DeploymentAppConfigForEnvironment, err := impl.pipelineBuilder.GetDeploymentConfigMap(refCdPipeline.EnvironmentId)
if err != nil {
impl.logger.Errorw("error in fetching deployment config for environment", "err", err)
}
for deploymentType, allowed := range DeploymentAppConfigForEnvironment {
AllowedDeploymentAppTypes[deploymentType] = allowed
}
isGitopsConfigured, err := impl.pipelineBuilder.IsGitopsConfigured()
if err != nil {
impl.logger.Errorw("error in checking if gitOps configured", "err", err)
return nil, err
}
var deploymentAppType string
if AllowedDeploymentAppTypes[util.PIPELINE_DEPLOYMENT_TYPE_ACD] && AllowedDeploymentAppTypes[util.PIPELINE_DEPLOYMENT_TYPE_HELM] {
deploymentAppType = refCdPipeline.DeploymentAppType
} else if AllowedDeploymentAppTypes[util.PIPELINE_DEPLOYMENT_TYPE_ACD] && isGitopsConfigured {
deploymentAppType = util.PIPELINE_DEPLOYMENT_TYPE_ACD
} else if AllowedDeploymentAppTypes[util.PIPELINE_DEPLOYMENT_TYPE_HELM] {
deploymentAppType = util.PIPELINE_DEPLOYMENT_TYPE_HELM
}
cdPipeline := &bean.CDPipelineConfigObject{
Id: 0,
EnvironmentId: refCdPipeline.EnvironmentId,
Expand All @@ -859,7 +884,7 @@ func (impl *AppCloneServiceImpl) CreateCdPipeline(req *cloneCdPipelineRequest, c
PostStageConfigMapSecretNames: refCdPipeline.PostStageConfigMapSecretNames,
RunPostStageInEnv: refCdPipeline.RunPostStageInEnv,
RunPreStageInEnv: refCdPipeline.RunPreStageInEnv,
DeploymentAppType: refCdPipeline.DeploymentAppType,
DeploymentAppType: deploymentAppType,
}
cdPipelineReq := &bean.CdPipelines{
Pipelines: []*bean.CDPipelineConfigObject{cdPipeline},
Expand Down
81 changes: 79 additions & 2 deletions pkg/attributes/AttributesService.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
package attributes

import (
"encoding/json"
"errors"
"fmt"
"github.com/devtron-labs/devtron/internal/sql/repository"
"github.com/go-pg/pg"
"go.uber.org/zap"
Expand All @@ -32,11 +35,13 @@ type AttributesService interface {
GetActiveList() ([]*AttributesDto, error)
GetByKey(key string) (*AttributesDto, error)
UpdateKeyValueByOne(key string) error
AddDeploymentEnforcementConfig(request *AttributesDto) (*AttributesDto, error)
}

const (
HostUrlKey string = "url"
API_SECRET_KEY string = "apiTokenSecret"
HostUrlKey string = "url"
API_SECRET_KEY string = "apiTokenSecret"
ENFORCE_DEPLOYMENT_TYPE_CONFIG string = "enforceDeploymentTypeConfig"
)

type AttributesDto struct {
Expand Down Expand Up @@ -233,3 +238,75 @@ func (impl AttributesServiceImpl) UpdateKeyValueByOne(key string) error {
return err

}

func (impl AttributesServiceImpl) AddDeploymentEnforcementConfig(request *AttributesDto) (*AttributesDto, error) {
model, err := impl.attributesRepository.FindByKey(ENFORCE_DEPLOYMENT_TYPE_CONFIG)
if err != nil && err != pg.ErrNoRows {
impl.logger.Errorw("error in fetching deploymentEnforcementConfig from db", "error", err, "key", request.Key)
return request, err
}
dbConnection := impl.attributesRepository.GetConnection()
tx, terr := dbConnection.Begin()
if terr != nil {
return request, terr
}
newConfig := make(map[string]map[string]bool)
err = json.Unmarshal([]byte(request.Value), &newConfig)
if err != nil {
return request, err
}
for environmentId, envConfig := range newConfig {
AllowedDeploymentAppTypes := 0
for _, allowed := range envConfig {
if allowed {
AllowedDeploymentAppTypes++
}
}
if AllowedDeploymentAppTypes == 0 && len(envConfig) > 0 {
return request, errors.New(fmt.Sprintf("Received invalid config for environment with id %s, "+
"at least one deployment app type should be allowed", environmentId))
}
}
// Rollback tx on error.
defer tx.Rollback()
if err == pg.ErrNoRows {
model := &repository.Attributes{
Key: ENFORCE_DEPLOYMENT_TYPE_CONFIG,
Value: request.Value,
}
model.Active = true
model.UpdatedOn = time.Now()
model.UpdatedBy = request.UserId
_, err = impl.attributesRepository.Save(model, tx)
if err != nil {
return request, err
}
} else {

oldConfig := make(map[string]map[string]bool)
oldConfigString := model.Value
//initialConfigString = `{ "1": {"argo_cd": true}}`
err = json.Unmarshal([]byte(oldConfigString), &oldConfig)
if err != nil {
return request, err
}
mergedConfig := oldConfig
for k, v := range newConfig {
mergedConfig[k] = v
}
value, err := json.Marshal(mergedConfig)
if err != nil {
return request, err
}
model.Value = string(value)
model.UpdatedOn = time.Now()
model.UpdatedBy = request.UserId
model.Active = true
err = impl.attributesRepository.Update(model, tx)
if err != nil {
return request, err
}
}
tx.Commit()
return request, nil
}
9 changes: 6 additions & 3 deletions pkg/cluster/EnvironmentService.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"encoding/json"
"fmt"
repository2 "github.com/devtron-labs/devtron/internal/sql/repository"
"github.com/devtron-labs/devtron/pkg/attributes"
"github.com/devtron-labs/devtron/pkg/user/bean"
"strconv"
"strings"
Expand Down Expand Up @@ -388,19 +389,21 @@ func (impl EnvironmentServiceImpl) GetEnvironmentListForAutocomplete(isDeploymen
if isDeploymentTypeParam {
for _, model := range models {
var (
deploymentConfig map[string]bool
allowedDeploymentConfigString []string
)
deploymentConfigValues, _ := impl.attributesRepository.FindByKey(fmt.Sprintf("%d", model.Id))
deploymentConfig := make(map[string]map[string]bool)
deploymentConfigEnvLevel := make(map[string]bool)
deploymentConfigValues, _ := impl.attributesRepository.FindByKey(attributes.ENFORCE_DEPLOYMENT_TYPE_CONFIG)
//if empty config received(doesn't exist in table) which can't be parsed
if deploymentConfigValues.Value != "" {
if err = json.Unmarshal([]byte(deploymentConfigValues.Value), &deploymentConfig); err != nil {
return nil, err
}
deploymentConfigEnvLevel, _ = deploymentConfig[fmt.Sprintf("%d", model.Id)]
}

// if real config along with absurd values exist in table {"argo_cd": true, "helm": false, "absurd": false}",
if ok, filteredDeploymentConfig := impl.IsReceivedDeploymentTypeValid(deploymentConfig); ok {
if ok, filteredDeploymentConfig := impl.IsReceivedDeploymentTypeValid(deploymentConfigEnvLevel); ok {
allowedDeploymentConfigString = filteredDeploymentConfig
} else {
allowedDeploymentConfigString = permittedDeploymentConfigString
Expand Down
75 changes: 69 additions & 6 deletions pkg/pipeline/GitopsOrHelmOption_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,70 @@ func TestGitopsOrHelmOption(t *testing.T) {
UserId: 0,
}
isGitOpsConfigured := true
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequest, isGitOpsConfigured)
deploymentConfig := make(map[string]bool)
deploymentConfig[bean.ArgoCd] = true
deploymentConfig[bean.Helm] = false
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequest, isGitOpsConfigured, deploymentConfig)

for _, pipeline := range pipelineCreateRequest.Pipelines {
assert.Equal(t, pipeline.DeploymentAppType, "argo_cd")
}

})
t.Run("DeploymentAppSetterFunctionIfGitOpsConfiguredButNotAllowedExternalUse", func(t *testing.T) {

sugaredLogger, err := util.NewSugardLogger()
assert.Nil(t, err)

pipelineBuilderService := NewPipelineBuilderImpl(sugaredLogger, nil, nil, nil, nil,
nil, nil, nil,
nil, nil, nil, nil, nil,
nil, nil, nil, nil, util.MergeUtil{Logger: sugaredLogger}, nil,
nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil,
nil, nil, nil,
nil, nil, nil, nil,
nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, &DeploymentServiceTypeConfig{IsInternalUse: false}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)

pipelineCreateRequest := &bean.CdPipelines{
Pipelines: []*bean.CDPipelineConfigObject{
{
Id: 0,
EnvironmentId: 1,
EnvironmentName: "",
CiPipelineId: 1,
TriggerType: "AUTOMATIC",
Name: "cd-1-vo8q",
Strategies: nil,
Namespace: "devtron-demo",
AppWorkflowId: 1,
DeploymentTemplate: "",
PreStage: bean.CdStage{},
PostStage: bean.CdStage{},
PreStageConfigMapSecretNames: bean.PreStageConfigMapSecretNames{},
PostStageConfigMapSecretNames: bean.PostStageConfigMapSecretNames{},
RunPreStageInEnv: false,
RunPostStageInEnv: false,
CdArgoSetup: false,
ParentPipelineId: 1,
ParentPipelineType: "CI_PIPELINE",
DeploymentAppType: "",
},
},
AppId: 1,
UserId: 0,
}
isGitOpsConfigured := true
deploymentConfig := make(map[string]bool)
deploymentConfig[bean.Helm] = true
deploymentConfig[bean.ArgoCd] = false
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequest, isGitOpsConfigured, deploymentConfig)

for _, pipeline := range pipelineCreateRequest.Pipelines {
assert.Equal(t, pipeline.DeploymentAppType, "helm")
}

})

t.Run("DeploymentAppSetterFunctionIfGitOpsNotConfiguredExternalUse", func(t *testing.T) {
Expand Down Expand Up @@ -108,7 +166,9 @@ func TestGitopsOrHelmOption(t *testing.T) {
UserId: 0,
}
isGitOpsConfigured := false
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequest, isGitOpsConfigured)
deploymentConfig := make(map[string]bool)
deploymentConfig[bean.Helm] = true
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequest, isGitOpsConfigured, deploymentConfig)

for _, pipeline := range pipelineCreateRequest.Pipelines {
assert.Equal(t, pipeline.DeploymentAppType, "helm")
Expand Down Expand Up @@ -161,7 +221,9 @@ func TestGitopsOrHelmOption(t *testing.T) {
UserId: 0,
}
isGitOpsConfigured := true
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequestHelm, isGitOpsConfigured)
deploymentConfig := make(map[string]bool)
deploymentConfig[bean.Helm] = true
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequestHelm, isGitOpsConfigured, deploymentConfig)

for _, pipeline := range pipelineCreateRequestHelm.Pipelines {
assert.Equal(t, pipeline.DeploymentAppType, "helm")
Expand Down Expand Up @@ -189,13 +251,14 @@ func TestGitopsOrHelmOption(t *testing.T) {
CdArgoSetup: false,
ParentPipelineId: 1,
ParentPipelineType: "CI_PIPELINE",
DeploymentAppType: "argo_cd",
DeploymentAppType: bean.ArgoCd,
},
},
AppId: 1,
UserId: 0,
}
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequestGitOps, isGitOpsConfigured)
deploymentConfig[bean.ArgoCd] = true
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequestGitOps, isGitOpsConfigured, deploymentConfig)

for _, pipeline := range pipelineCreateRequestGitOps.Pipelines {
assert.Equal(t, pipeline.DeploymentAppType, "argo_cd")
Expand Down Expand Up @@ -302,7 +365,7 @@ func TestGitopsOrHelmOption(t *testing.T) {
CdArgoSetup: false,
ParentPipelineId: 1,
ParentPipelineType: "CI_PIPELINE",
DeploymentAppType: "argo_cd",
DeploymentAppType: bean.ArgoCd,
},
},
AppId: 1,
Expand Down
Loading

0 comments on commit bd37887

Please sign in to comment.