Skip to content

Commit bd37887

Browse files
authored
enhancement: enforcing deployment type in environment (#3616)
* deployment type allowed config fixes * wip: app clone fix * fixing test * pr review comments
1 parent e9223a6 commit bd37887

File tree

8 files changed

+323
-119
lines changed

8 files changed

+323
-119
lines changed

api/restHandler/AttributesRestHandlder.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type AttributesRestHandler interface {
3636
GetAttributesById(w http.ResponseWriter, r *http.Request)
3737
GetAttributesActiveList(w http.ResponseWriter, r *http.Request)
3838
GetAttributesByKey(w http.ResponseWriter, r *http.Request)
39+
AddDeploymentEnforcementConfig(w http.ResponseWriter, r *http.Request)
3940
}
4041

4142
type AttributesRestHandlerImpl struct {
@@ -191,3 +192,34 @@ func (handler AttributesRestHandlerImpl) GetAttributesByKey(w http.ResponseWrite
191192
}
192193
common.WriteJsonResp(w, nil, res, http.StatusOK)
193194
}
195+
196+
func (handler AttributesRestHandlerImpl) AddDeploymentEnforcementConfig(w http.ResponseWriter, r *http.Request) {
197+
userId, err := handler.userService.GetLoggedInUser(r)
198+
if userId == 0 || err != nil {
199+
common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized)
200+
return
201+
}
202+
decoder := json.NewDecoder(r.Body)
203+
var dto attributes.AttributesDto
204+
err = decoder.Decode(&dto)
205+
if err != nil {
206+
handler.logger.Errorw("request err, AddAttributes", "err", err, "payload", dto)
207+
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
208+
return
209+
}
210+
211+
token := r.Header.Get("token")
212+
if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionCreate, "*"); !ok {
213+
common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden)
214+
return
215+
}
216+
217+
handler.logger.Infow("request payload, AddAttributes", "payload", dto)
218+
resp, err := handler.attributesService.AddDeploymentEnforcementConfig(&dto)
219+
if err != nil {
220+
handler.logger.Errorw("service err, AddAttributes", "err", err, "payload", dto)
221+
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
222+
return
223+
}
224+
common.WriteJsonResp(w, nil, resp, http.StatusOK)
225+
}

api/router/AttributesRouter.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,6 @@ func (router AttributesRouterImpl) InitAttributesRouter(attributesRouter *mux.Ro
4848
HandlerFunc(router.attributesRestHandler.GetAttributesById).Methods("GET")
4949
attributesRouter.Path("/active/list").
5050
HandlerFunc(router.attributesRestHandler.GetAttributesActiveList).Methods("GET")
51+
attributesRouter.Path("/create/deploymentConfig").
52+
HandlerFunc(router.attributesRestHandler.AddDeploymentEnforcementConfig).Methods("POST")
5153
}

pkg/appClone/AppCloneService.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,31 @@ func (impl *AppCloneServiceImpl) CreateCdPipeline(req *cloneCdPipelineRequest, c
843843
if strings.HasPrefix(pipelineName, req.refAppName) {
844844
pipelineName = strings.Replace(pipelineName, req.refAppName+"-", "", 1)
845845
}
846+
// by default all deployment types are allowed
847+
AllowedDeploymentAppTypes := map[string]bool{
848+
util.PIPELINE_DEPLOYMENT_TYPE_ACD: true,
849+
util.PIPELINE_DEPLOYMENT_TYPE_HELM: true,
850+
}
851+
DeploymentAppConfigForEnvironment, err := impl.pipelineBuilder.GetDeploymentConfigMap(refCdPipeline.EnvironmentId)
852+
if err != nil {
853+
impl.logger.Errorw("error in fetching deployment config for environment", "err", err)
854+
}
855+
for deploymentType, allowed := range DeploymentAppConfigForEnvironment {
856+
AllowedDeploymentAppTypes[deploymentType] = allowed
857+
}
858+
isGitopsConfigured, err := impl.pipelineBuilder.IsGitopsConfigured()
859+
if err != nil {
860+
impl.logger.Errorw("error in checking if gitOps configured", "err", err)
861+
return nil, err
862+
}
863+
var deploymentAppType string
864+
if AllowedDeploymentAppTypes[util.PIPELINE_DEPLOYMENT_TYPE_ACD] && AllowedDeploymentAppTypes[util.PIPELINE_DEPLOYMENT_TYPE_HELM] {
865+
deploymentAppType = refCdPipeline.DeploymentAppType
866+
} else if AllowedDeploymentAppTypes[util.PIPELINE_DEPLOYMENT_TYPE_ACD] && isGitopsConfigured {
867+
deploymentAppType = util.PIPELINE_DEPLOYMENT_TYPE_ACD
868+
} else if AllowedDeploymentAppTypes[util.PIPELINE_DEPLOYMENT_TYPE_HELM] {
869+
deploymentAppType = util.PIPELINE_DEPLOYMENT_TYPE_HELM
870+
}
846871
cdPipeline := &bean.CDPipelineConfigObject{
847872
Id: 0,
848873
EnvironmentId: refCdPipeline.EnvironmentId,
@@ -859,7 +884,7 @@ func (impl *AppCloneServiceImpl) CreateCdPipeline(req *cloneCdPipelineRequest, c
859884
PostStageConfigMapSecretNames: refCdPipeline.PostStageConfigMapSecretNames,
860885
RunPostStageInEnv: refCdPipeline.RunPostStageInEnv,
861886
RunPreStageInEnv: refCdPipeline.RunPreStageInEnv,
862-
DeploymentAppType: refCdPipeline.DeploymentAppType,
887+
DeploymentAppType: deploymentAppType,
863888
}
864889
cdPipelineReq := &bean.CdPipelines{
865890
Pipelines: []*bean.CDPipelineConfigObject{cdPipeline},

pkg/attributes/AttributesService.go

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
package attributes
1919

2020
import (
21+
"encoding/json"
22+
"errors"
23+
"fmt"
2124
"github.com/devtron-labs/devtron/internal/sql/repository"
2225
"github.com/go-pg/pg"
2326
"go.uber.org/zap"
@@ -32,11 +35,13 @@ type AttributesService interface {
3235
GetActiveList() ([]*AttributesDto, error)
3336
GetByKey(key string) (*AttributesDto, error)
3437
UpdateKeyValueByOne(key string) error
38+
AddDeploymentEnforcementConfig(request *AttributesDto) (*AttributesDto, error)
3539
}
3640

3741
const (
38-
HostUrlKey string = "url"
39-
API_SECRET_KEY string = "apiTokenSecret"
42+
HostUrlKey string = "url"
43+
API_SECRET_KEY string = "apiTokenSecret"
44+
ENFORCE_DEPLOYMENT_TYPE_CONFIG string = "enforceDeploymentTypeConfig"
4045
)
4146

4247
type AttributesDto struct {
@@ -233,3 +238,75 @@ func (impl AttributesServiceImpl) UpdateKeyValueByOne(key string) error {
233238
return err
234239

235240
}
241+
242+
func (impl AttributesServiceImpl) AddDeploymentEnforcementConfig(request *AttributesDto) (*AttributesDto, error) {
243+
model, err := impl.attributesRepository.FindByKey(ENFORCE_DEPLOYMENT_TYPE_CONFIG)
244+
if err != nil && err != pg.ErrNoRows {
245+
impl.logger.Errorw("error in fetching deploymentEnforcementConfig from db", "error", err, "key", request.Key)
246+
return request, err
247+
}
248+
dbConnection := impl.attributesRepository.GetConnection()
249+
tx, terr := dbConnection.Begin()
250+
if terr != nil {
251+
return request, terr
252+
}
253+
newConfig := make(map[string]map[string]bool)
254+
err = json.Unmarshal([]byte(request.Value), &newConfig)
255+
if err != nil {
256+
return request, err
257+
}
258+
for environmentId, envConfig := range newConfig {
259+
AllowedDeploymentAppTypes := 0
260+
for _, allowed := range envConfig {
261+
if allowed {
262+
AllowedDeploymentAppTypes++
263+
}
264+
}
265+
if AllowedDeploymentAppTypes == 0 && len(envConfig) > 0 {
266+
return request, errors.New(fmt.Sprintf("Received invalid config for environment with id %s, "+
267+
"at least one deployment app type should be allowed", environmentId))
268+
}
269+
}
270+
// Rollback tx on error.
271+
defer tx.Rollback()
272+
if err == pg.ErrNoRows {
273+
model := &repository.Attributes{
274+
Key: ENFORCE_DEPLOYMENT_TYPE_CONFIG,
275+
Value: request.Value,
276+
}
277+
model.Active = true
278+
model.UpdatedOn = time.Now()
279+
model.UpdatedBy = request.UserId
280+
_, err = impl.attributesRepository.Save(model, tx)
281+
if err != nil {
282+
return request, err
283+
}
284+
} else {
285+
286+
oldConfig := make(map[string]map[string]bool)
287+
oldConfigString := model.Value
288+
//initialConfigString = `{ "1": {"argo_cd": true}}`
289+
err = json.Unmarshal([]byte(oldConfigString), &oldConfig)
290+
if err != nil {
291+
return request, err
292+
}
293+
mergedConfig := oldConfig
294+
for k, v := range newConfig {
295+
mergedConfig[k] = v
296+
}
297+
value, err := json.Marshal(mergedConfig)
298+
if err != nil {
299+
return request, err
300+
}
301+
model.Value = string(value)
302+
model.UpdatedOn = time.Now()
303+
model.UpdatedBy = request.UserId
304+
model.Active = true
305+
err = impl.attributesRepository.Update(model, tx)
306+
if err != nil {
307+
return request, err
308+
}
309+
}
310+
tx.Commit()
311+
return request, nil
312+
}

pkg/cluster/EnvironmentService.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"encoding/json"
2222
"fmt"
2323
repository2 "github.com/devtron-labs/devtron/internal/sql/repository"
24+
"github.com/devtron-labs/devtron/pkg/attributes"
2425
"github.com/devtron-labs/devtron/pkg/user/bean"
2526
"strconv"
2627
"strings"
@@ -388,19 +389,21 @@ func (impl EnvironmentServiceImpl) GetEnvironmentListForAutocomplete(isDeploymen
388389
if isDeploymentTypeParam {
389390
for _, model := range models {
390391
var (
391-
deploymentConfig map[string]bool
392392
allowedDeploymentConfigString []string
393393
)
394-
deploymentConfigValues, _ := impl.attributesRepository.FindByKey(fmt.Sprintf("%d", model.Id))
394+
deploymentConfig := make(map[string]map[string]bool)
395+
deploymentConfigEnvLevel := make(map[string]bool)
396+
deploymentConfigValues, _ := impl.attributesRepository.FindByKey(attributes.ENFORCE_DEPLOYMENT_TYPE_CONFIG)
395397
//if empty config received(doesn't exist in table) which can't be parsed
396398
if deploymentConfigValues.Value != "" {
397399
if err = json.Unmarshal([]byte(deploymentConfigValues.Value), &deploymentConfig); err != nil {
398400
return nil, err
399401
}
402+
deploymentConfigEnvLevel, _ = deploymentConfig[fmt.Sprintf("%d", model.Id)]
400403
}
401404

402405
// if real config along with absurd values exist in table {"argo_cd": true, "helm": false, "absurd": false}",
403-
if ok, filteredDeploymentConfig := impl.IsReceivedDeploymentTypeValid(deploymentConfig); ok {
406+
if ok, filteredDeploymentConfig := impl.IsReceivedDeploymentTypeValid(deploymentConfigEnvLevel); ok {
404407
allowedDeploymentConfigString = filteredDeploymentConfig
405408
} else {
406409
allowedDeploymentConfigString = permittedDeploymentConfigString

pkg/pipeline/GitopsOrHelmOption_test.go

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,70 @@ func TestGitopsOrHelmOption(t *testing.T) {
5555
UserId: 0,
5656
}
5757
isGitOpsConfigured := true
58-
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequest, isGitOpsConfigured)
58+
deploymentConfig := make(map[string]bool)
59+
deploymentConfig[bean.ArgoCd] = true
60+
deploymentConfig[bean.Helm] = false
61+
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequest, isGitOpsConfigured, deploymentConfig)
5962

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

67+
})
68+
t.Run("DeploymentAppSetterFunctionIfGitOpsConfiguredButNotAllowedExternalUse", func(t *testing.T) {
69+
70+
sugaredLogger, err := util.NewSugardLogger()
71+
assert.Nil(t, err)
72+
73+
pipelineBuilderService := NewPipelineBuilderImpl(sugaredLogger, nil, nil, nil, nil,
74+
nil, nil, nil,
75+
nil, nil, nil, nil, nil,
76+
nil, nil, nil, nil, util.MergeUtil{Logger: sugaredLogger}, nil,
77+
nil, nil, nil, nil, nil,
78+
nil, nil, nil, nil, nil,
79+
nil, nil, nil,
80+
nil, nil, nil, nil,
81+
nil, nil, nil, nil,
82+
nil, nil, nil, nil, nil, nil, nil, nil, &DeploymentServiceTypeConfig{IsInternalUse: false}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
83+
84+
pipelineCreateRequest := &bean.CdPipelines{
85+
Pipelines: []*bean.CDPipelineConfigObject{
86+
{
87+
Id: 0,
88+
EnvironmentId: 1,
89+
EnvironmentName: "",
90+
CiPipelineId: 1,
91+
TriggerType: "AUTOMATIC",
92+
Name: "cd-1-vo8q",
93+
Strategies: nil,
94+
Namespace: "devtron-demo",
95+
AppWorkflowId: 1,
96+
DeploymentTemplate: "",
97+
PreStage: bean.CdStage{},
98+
PostStage: bean.CdStage{},
99+
PreStageConfigMapSecretNames: bean.PreStageConfigMapSecretNames{},
100+
PostStageConfigMapSecretNames: bean.PostStageConfigMapSecretNames{},
101+
RunPreStageInEnv: false,
102+
RunPostStageInEnv: false,
103+
CdArgoSetup: false,
104+
ParentPipelineId: 1,
105+
ParentPipelineType: "CI_PIPELINE",
106+
DeploymentAppType: "",
107+
},
108+
},
109+
AppId: 1,
110+
UserId: 0,
111+
}
112+
isGitOpsConfigured := true
113+
deploymentConfig := make(map[string]bool)
114+
deploymentConfig[bean.Helm] = true
115+
deploymentConfig[bean.ArgoCd] = false
116+
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequest, isGitOpsConfigured, deploymentConfig)
117+
118+
for _, pipeline := range pipelineCreateRequest.Pipelines {
119+
assert.Equal(t, pipeline.DeploymentAppType, "helm")
120+
}
121+
64122
})
65123

66124
t.Run("DeploymentAppSetterFunctionIfGitOpsNotConfiguredExternalUse", func(t *testing.T) {
@@ -108,7 +166,9 @@ func TestGitopsOrHelmOption(t *testing.T) {
108166
UserId: 0,
109167
}
110168
isGitOpsConfigured := false
111-
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequest, isGitOpsConfigured)
169+
deploymentConfig := make(map[string]bool)
170+
deploymentConfig[bean.Helm] = true
171+
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequest, isGitOpsConfigured, deploymentConfig)
112172

113173
for _, pipeline := range pipelineCreateRequest.Pipelines {
114174
assert.Equal(t, pipeline.DeploymentAppType, "helm")
@@ -161,7 +221,9 @@ func TestGitopsOrHelmOption(t *testing.T) {
161221
UserId: 0,
162222
}
163223
isGitOpsConfigured := true
164-
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequestHelm, isGitOpsConfigured)
224+
deploymentConfig := make(map[string]bool)
225+
deploymentConfig[bean.Helm] = true
226+
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequestHelm, isGitOpsConfigured, deploymentConfig)
165227

166228
for _, pipeline := range pipelineCreateRequestHelm.Pipelines {
167229
assert.Equal(t, pipeline.DeploymentAppType, "helm")
@@ -189,13 +251,14 @@ func TestGitopsOrHelmOption(t *testing.T) {
189251
CdArgoSetup: false,
190252
ParentPipelineId: 1,
191253
ParentPipelineType: "CI_PIPELINE",
192-
DeploymentAppType: "argo_cd",
254+
DeploymentAppType: bean.ArgoCd,
193255
},
194256
},
195257
AppId: 1,
196258
UserId: 0,
197259
}
198-
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequestGitOps, isGitOpsConfigured)
260+
deploymentConfig[bean.ArgoCd] = true
261+
pipelineBuilderService.SetPipelineDeploymentAppType(pipelineCreateRequestGitOps, isGitOpsConfigured, deploymentConfig)
199262

200263
for _, pipeline := range pipelineCreateRequestGitOps.Pipelines {
201264
assert.Equal(t, pipeline.DeploymentAppType, "argo_cd")
@@ -302,7 +365,7 @@ func TestGitopsOrHelmOption(t *testing.T) {
302365
CdArgoSetup: false,
303366
ParentPipelineId: 1,
304367
ParentPipelineType: "CI_PIPELINE",
305-
DeploymentAppType: "argo_cd",
368+
DeploymentAppType: bean.ArgoCd,
306369
},
307370
},
308371
AppId: 1,

0 commit comments

Comments
 (0)