Skip to content

Commit b1f528a

Browse files
authored
IDP application implementation (#4277)
* IDP application implementation Signed-off-by: Min Min <[email protected]> * add missing files Signed-off-by: Min Min <[email protected]> * add plugin tab filter feature Signed-off-by: Min Min <[email protected]> * change api field name Signed-off-by: Min Min <[email protected]> * debug Signed-off-by: Min Min <[email protected]> * fix minor problems Signed-off-by: Min Min <[email protected]> * fix minor bugs Signed-off-by: Min Min <[email protected]> * add validation for update function in field definition Signed-off-by: Min Min <[email protected]> * debug Signed-off-by: Min Min <[email protected]> * add default field Signed-off-by: Min Min <[email protected]> * debug Signed-off-by: Min Min <[email protected]> * debug Signed-off-by: Min Min <[email protected]> * add service id response Signed-off-by: Min Min <[email protected]> * debug Signed-off-by: Min Min <[email protected]> * debug Signed-off-by: Min Min <[email protected]> * debug Signed-off-by: Min Min <[email protected]> * debug Signed-off-by: Min Min <[email protected]> * debug Signed-off-by: Min Min <[email protected]> * debug Signed-off-by: Min Min <[email protected]> * add list env api and change update application logic Signed-off-by: Min Min <[email protected]> * debug Signed-off-by: Min Min <[email protected]> * debug Signed-off-by: Min Min <[email protected]> * debug Signed-off-by: Min Min <[email protected]> * update filter Signed-off-by: Min Min <[email protected]> * add bulk create application api Signed-off-by: Min Min <[email protected]> * add index Signed-off-by: Min Min <[email protected]> * add uniqueness check Signed-off-by: Min Min <[email protected]> * change filter field name Signed-off-by: Min Min <[email protected]> * add build-in field data initialiazation for source Signed-off-by: Min Min <[email protected]> * debug Signed-off-by: Min Min <[email protected]> * add delete field logic and add some field support Signed-off-by: Min Min <[email protected]> * update for efficiency Signed-off-by: Min Min <[email protected]> * escape built-in custom field Signed-off-by: Min Min <[email protected]> * debug Signed-off-by: Min Min <[email protected]> * debug Signed-off-by: Min Min <[email protected]> --------- Signed-off-by: Min Min <[email protected]>
1 parent 8f35848 commit b1f528a

File tree

12 files changed

+2192
-3
lines changed

12 files changed

+2192
-3
lines changed

pkg/cli/initconfig/cmd/init.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ import (
4242
gormtool "github.com/koderover/zadig/v2/pkg/tool/gorm"
4343
"github.com/koderover/zadig/v2/pkg/tool/log"
4444
mongotool "github.com/koderover/zadig/v2/pkg/tool/mongo"
45+
46+
aslanconfig "github.com/koderover/zadig/v2/pkg/microservice/aslan/config"
47+
commonmodels "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/repository/models"
48+
49+
"errors"
50+
51+
"go.mongodb.org/mongo-driver/mongo"
4552
)
4653

4754
func init() {
@@ -111,6 +118,8 @@ func createOrUpdateMongodbIndex(ctx context.Context) {
111118
for _, r := range []indexer{
112119
// aslan related db index
113120
template.NewProductColl(),
121+
commonrepo.NewApplicationColl(),
122+
commonrepo.NewApplicationFieldDefinitionColl(),
114123
commonrepo.NewBasicImageColl(),
115124
commonrepo.NewBuildColl(),
116125
commonrepo.NewCallbackRequestColl(),
@@ -252,6 +261,12 @@ func initSystemData() error {
252261
return err
253262
}
254263

264+
// Seed built-in application field definitions (idempotent)
265+
if err := createBuiltinApplicationFieldDefinitions(); err != nil {
266+
log.Errorf("failed to create builtin application field definitions: %s", err)
267+
return err
268+
}
269+
255270
if err := createLocalCluster(); err != nil {
256271
log.Errorf("createLocalCluster err:%s", err)
257272
return err
@@ -268,6 +283,73 @@ func initSystemData() error {
268283
return nil
269284
}
270285

286+
// createBuiltinApplicationFieldDefinitions creates built-in Application fields into the
287+
// application_field_definition collection.
288+
func createBuiltinApplicationFieldDefinitions() error {
289+
coll := commonrepo.NewApplicationFieldDefinitionColl()
290+
ctx := context.Background()
291+
292+
// Define built-in fields. Ignore _id and plugins; treat repository as a special type without inner-field visibility.
293+
builtin := []commonmodels.ApplicationFieldDefinition{
294+
{Key: "name", Name: "名称", Type: aslanconfig.ApplicationCustomFieldTypeText, Required: true, ShowInList: true, Source: aslanconfig.ApplicationFieldSourceBuiltin, Description: "业务服务的名称"},
295+
{Key: "key", Name: "标识", Type: aslanconfig.ApplicationCustomFieldTypeText, Required: true, ShowInList: true, Source: aslanconfig.ApplicationFieldSourceBuiltin, Description: "业务服务的唯一标识符"},
296+
{Key: "project", Name: "项目归属", Type: aslanconfig.ApplicationCustomFieldTypeProject, Required: true, ShowInList: true, Source: aslanconfig.ApplicationFieldSourceBuiltin, Description: "业务服务所属的项目"},
297+
{Key: "type", Name: "类型", Type: aslanconfig.ApplicationCustomFieldTypeSingleSelect, ShowInList: true, Source: aslanconfig.ApplicationFieldSourceBuiltin, Description: "业务服务的类型", Options: []string{"服务", "组件", "应用"}},
298+
{Key: "owner", Name: "负责人", Type: aslanconfig.ApplicationCustomFieldTypeUser, ShowInList: true, Source: aslanconfig.ApplicationFieldSourceBuiltin, Description: "业务服务的负责人"},
299+
{Key: "repository", Name: "代码库", Type: aslanconfig.ApplicationCustomFieldTypeRepository, ShowInList: true, Source: aslanconfig.ApplicationFieldSourceBuiltin, Description: "代码仓库"},
300+
{Key: "description", Name: "描述", Type: aslanconfig.ApplicationCustomFieldTypeText, ShowInList: true, Source: aslanconfig.ApplicationFieldSourceBuiltin, Description: "业务服务的详细描述"},
301+
{Key: "testing_service_name", Name: "测试配置", Type: aslanconfig.ApplicationCustomFieldTypeServiceConfig, ShowInList: true, Source: aslanconfig.ApplicationFieldSourceBuiltin, Description: "测试环境的配置"},
302+
{Key: "production_service_name", Name: "生产配置", Type: aslanconfig.ApplicationCustomFieldTypeServiceConfig, ShowInList: true, Source: aslanconfig.ApplicationFieldSourceBuiltin, Description: "生产环境的配置"},
303+
{Key: "create_time", Name: "创建时间", Type: aslanconfig.ApplicationCustomFieldTypeDatetime, ShowInList: true, Source: aslanconfig.ApplicationFieldSourceBuiltin, Description: "业务服务的创建时间"},
304+
{Key: "update_time", Name: "更新时间", Type: aslanconfig.ApplicationCustomFieldTypeDatetime, ShowInList: true, Source: aslanconfig.ApplicationFieldSourceBuiltin, Description: "业务服务的更新时间"},
305+
}
306+
307+
// Upsert per key to be idempotent. Keep user-changed attributes for custom fields; for built-ins we only enforce Source="builtin" and Type.
308+
for i := range builtin {
309+
b := builtin[i]
310+
existing, err := coll.GetByKey(ctx, b.Key)
311+
if err != nil {
312+
// if not found, create
313+
if errors.Is(err, mongo.ErrNoDocuments) {
314+
// set timestamps via Create
315+
if _, cerr := coll.Create(ctx, &b); cerr != nil {
316+
return cerr
317+
}
318+
continue
319+
}
320+
return err
321+
}
322+
// Already exists: only update Source, Type, Name, Description, Required if they differ; preserve other settings (e.g., ShowInList) to avoid surprising overrides.
323+
needUpdate := false
324+
if existing.Source != aslanconfig.ApplicationFieldSourceBuiltin {
325+
existing.Source = aslanconfig.ApplicationFieldSourceBuiltin
326+
needUpdate = true
327+
}
328+
if existing.Type != b.Type {
329+
existing.Type = b.Type
330+
needUpdate = true
331+
}
332+
if existing.Name != b.Name {
333+
existing.Name = b.Name
334+
needUpdate = true
335+
}
336+
if existing.Description != b.Description {
337+
existing.Description = b.Description
338+
needUpdate = true
339+
}
340+
if existing.Required != b.Required {
341+
existing.Required = b.Required
342+
needUpdate = true
343+
}
344+
if needUpdate {
345+
if err := coll.UpdateByID(ctx, existing.ID.Hex(), existing); err != nil {
346+
return err
347+
}
348+
}
349+
}
350+
return nil
351+
}
352+
271353
func createLocalCluster() error {
272354
cluster, err := aslan.New(config.AslanServiceAddress()).GetLocalCluster()
273355
if err != nil {

pkg/microservice/aslan/config/consts.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,3 +766,71 @@ const (
766766
NavigationKeyDataInsight NavigationItemKey = "dataInsight"
767767
NavigationKeyCustomerDelivery NavigationItemKey = "customerDelivery"
768768
)
769+
770+
// Application custom field types
771+
type ApplicationCustomFieldType string
772+
773+
const (
774+
ApplicationCustomFieldTypeText ApplicationCustomFieldType = "text"
775+
ApplicationCustomFieldTypeNumber ApplicationCustomFieldType = "number"
776+
ApplicationCustomFieldTypeBool ApplicationCustomFieldType = "bool"
777+
ApplicationCustomFieldTypeDatetime ApplicationCustomFieldType = "datetime"
778+
ApplicationCustomFieldTypeMultiSelect ApplicationCustomFieldType = "multi_select"
779+
// below is the custom field types that are only supported by the frontend, but has no real meaning in the backend
780+
ApplicationCustomFieldTypeLink ApplicationCustomFieldType = "link"
781+
ApplicationCustomFieldTypeUser ApplicationCustomFieldType = "user"
782+
ApplicationCustomFieldTypeUserGroup ApplicationCustomFieldType = "user_group"
783+
ApplicationCustomFieldTypeRepository ApplicationCustomFieldType = "repository"
784+
ApplicationCustomFieldTypeProject ApplicationCustomFieldType = "project"
785+
ApplicationCustomFieldTypeSingleSelect ApplicationCustomFieldType = "single_select"
786+
ApplicationCustomFieldTypeServiceConfig ApplicationCustomFieldType = "service_config"
787+
)
788+
789+
// Application field source types (definition origin)
790+
type ApplicationFieldSourceType string
791+
792+
const (
793+
ApplicationFieldSourceBuiltin ApplicationFieldSourceType = "builtin"
794+
ApplicationFieldSourceCustom ApplicationFieldSourceType = "custom"
795+
)
796+
797+
// Application filter field categories used by search filtering
798+
type ApplicationFilterFieldType string
799+
800+
const (
801+
ApplicationFilterFieldTypeString ApplicationFilterFieldType = "string"
802+
ApplicationFilterFieldTypeNumber ApplicationFilterFieldType = "number"
803+
ApplicationFilterFieldTypeBool ApplicationFilterFieldType = "bool"
804+
ApplicationFilterFieldTypeArray ApplicationFilterFieldType = "array"
805+
)
806+
807+
// Application filter actions (verbs) supported by search filtering
808+
type ApplicationFilterAction string
809+
810+
const (
811+
// common
812+
ApplicationFilterActionEq ApplicationFilterAction = "="
813+
ApplicationFilterActionNe ApplicationFilterAction = "!="
814+
ApplicationFilterActionHasAnyOf ApplicationFilterAction = "has_any_of"
815+
ApplicationFilterActionContains ApplicationFilterAction = "contains"
816+
ApplicationFilterActionNotContains ApplicationFilterAction = "not_contains"
817+
818+
// string-like
819+
ApplicationFilterActionBeginsWith ApplicationFilterAction = "begins_with"
820+
ApplicationFilterActionNotBeginsWith ApplicationFilterAction = "not_begins_with"
821+
ApplicationFilterActionEndsWith ApplicationFilterAction = "ends_with"
822+
ApplicationFilterActionNotEndsWith ApplicationFilterAction = "not_ends_with"
823+
824+
// number
825+
ApplicationFilterActionLt ApplicationFilterAction = "<"
826+
ApplicationFilterActionLte ApplicationFilterAction = "<="
827+
ApplicationFilterActionGt ApplicationFilterAction = ">"
828+
ApplicationFilterActionGte ApplicationFilterAction = ">="
829+
830+
// bool
831+
ApplicationFilterActionIs ApplicationFilterAction = "is"
832+
833+
// array
834+
ApplicationFilterActionIsEmpty ApplicationFilterAction = "is_empty"
835+
ApplicationFilterActionIsNotEmpty ApplicationFilterAction = "is_not_empty"
836+
)
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
Copyright 2025 The KodeRover Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package handler
18+
19+
import (
20+
"bytes"
21+
"encoding/json"
22+
"fmt"
23+
"io"
24+
25+
"github.com/gin-gonic/gin"
26+
"github.com/gin-gonic/gin/binding"
27+
28+
"github.com/koderover/zadig/v2/pkg/microservice/aslan/core/application/service"
29+
commonmodels "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/repository/models"
30+
internalhandler "github.com/koderover/zadig/v2/pkg/shared/handler"
31+
e "github.com/koderover/zadig/v2/pkg/tool/errors"
32+
)
33+
34+
func CreateApplication(c *gin.Context) {
35+
ctx, err := internalhandler.NewContextWithAuthorization(c)
36+
defer func() { internalhandler.JSONResponse(c, ctx) }()
37+
if err != nil {
38+
ctx.RespErr = fmt.Errorf("authorization Info Generation failed: err %s", err)
39+
ctx.UnAuthorized = true
40+
return
41+
}
42+
args := new(commonmodels.Application)
43+
data, _ := c.GetRawData()
44+
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
45+
if err := c.ShouldBindWith(&args, binding.JSON); err != nil {
46+
ctx.RespErr = e.ErrInvalidParam.AddDesc("invalid application args")
47+
return
48+
}
49+
ctx.Resp, ctx.RespErr = service.CreateApplication(args, ctx.Logger)
50+
}
51+
52+
func BulkCreateApplications(c *gin.Context) {
53+
ctx, err := internalhandler.NewContextWithAuthorization(c)
54+
defer func() { internalhandler.JSONResponse(c, ctx) }()
55+
if err != nil {
56+
ctx.RespErr = fmt.Errorf("authorization Info Generation failed: err %s", err)
57+
ctx.UnAuthorized = true
58+
return
59+
}
60+
61+
var args []*commonmodels.Application
62+
data, _ := c.GetRawData()
63+
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
64+
if err := c.ShouldBindWith(&args, binding.JSON); err != nil {
65+
ctx.RespErr = e.ErrInvalidParam.AddDesc("invalid application args")
66+
return
67+
}
68+
69+
err = service.BulkCreateApplications(args, ctx.Logger)
70+
if err != nil {
71+
ctx.RespErr = err
72+
return
73+
}
74+
}
75+
76+
func GetApplication(c *gin.Context) {
77+
ctx := internalhandler.NewContext(c)
78+
defer func() { internalhandler.JSONResponse(c, ctx) }()
79+
ctx.Resp, ctx.RespErr = service.GetApplication(c.Param("id"), ctx.Logger)
80+
}
81+
82+
func UpdateApplication(c *gin.Context) {
83+
ctx, err := internalhandler.NewContextWithAuthorization(c)
84+
defer func() { internalhandler.JSONResponse(c, ctx) }()
85+
if err != nil {
86+
ctx.RespErr = fmt.Errorf("authorization Info Generation failed: err %s", err)
87+
ctx.UnAuthorized = true
88+
return
89+
}
90+
args := new(commonmodels.Application)
91+
data, _ := c.GetRawData()
92+
if err := json.Unmarshal(data, args); err != nil {
93+
ctx.RespErr = e.ErrInvalidParam.AddDesc("invalid application args")
94+
return
95+
}
96+
ctx.RespErr = service.UpdateApplication(c.Param("id"), args, ctx.Logger)
97+
}
98+
99+
func DeleteApplication(c *gin.Context) {
100+
ctx, err := internalhandler.NewContextWithAuthorization(c)
101+
defer func() { internalhandler.JSONResponse(c, ctx) }()
102+
if err != nil {
103+
ctx.RespErr = fmt.Errorf("authorization Info Generation failed: err %s", err)
104+
ctx.UnAuthorized = true
105+
return
106+
}
107+
ctx.RespErr = service.DeleteApplication(c.Param("id"), ctx.Logger)
108+
}
109+
110+
func SearchApplications(c *gin.Context) {
111+
ctx := internalhandler.NewContext(c)
112+
defer func() { internalhandler.JSONResponse(c, ctx) }()
113+
var req service.SearchApplicationsRequest
114+
if err := c.ShouldBindJSON(&req); err != nil {
115+
ctx.RespErr = e.ErrInvalidParam.AddErr(err)
116+
return
117+
}
118+
items, total, err := service.SearchApplications(&req, ctx.Logger)
119+
if err != nil {
120+
ctx.RespErr = err
121+
return
122+
}
123+
ctx.Resp = gin.H{"items": items, "page": req.Page, "page_size": req.PageSize, "total": total}
124+
}
125+
126+
func ListApplicationEnvs(c *gin.Context) {
127+
ctx := internalhandler.NewContext(c)
128+
defer func() { internalhandler.JSONResponse(c, ctx) }()
129+
resp, err := service.ListApplicationEnvs(c.Param("id"), ctx.Logger)
130+
if err != nil {
131+
ctx.RespErr = err
132+
return
133+
}
134+
ctx.Resp = resp
135+
}

0 commit comments

Comments
 (0)