diff --git a/devcloud/README.md b/devcloud/README.md index 95954e5..43f44f1 100644 --- a/devcloud/README.md +++ b/devcloud/README.md @@ -6,8 +6,8 @@ devcloud: 研发云, 给产研团队(技术团队), 产品经理, 项目经理, + 需求管理: Jira, 禅道, ...(x) + 应用管理: 立项后的 SCM 源代码管理, 应用的元数据, 服务树(服务分组) + 资源管理: CMDB -+ 应用构建: CI, 流水线发布, 应用的持续构建, Jenkins, 新一代的流程, 基于K8s Job自己设计 + 发布中心(Dev/Test/Pre/Pro)CD: 发布, 应用维护, 部署集群的维护 ++ 应用构建: CI, 流水线发布, 应用的持续构建, Jenkins, 新一代的流程, 基于K8s Job自己设计 多业务模块组成, 渐进式微服务开发方式 diff --git a/devcloud/main.go b/devcloud/main.go index 9b47c04..d45c56d 100644 --- a/devcloud/main.go +++ b/devcloud/main.go @@ -7,6 +7,7 @@ import ( _ "122.51.31.227/go-course/go18/devcloud/mcenter/apps" // audit 业务对象 _ "122.51.31.227/go-course/go18/devcloud/audit/apps" + // mpaas 应用发布 // 非功能性模块 _ "github.com/infraboard/mcube/v2/ioc/apps/apidoc/restful" diff --git a/devcloud/mcenter/apps/policy/impl/permission.go b/devcloud/mcenter/apps/policy/impl/permission.go index 0f20c96..9a5a66a 100644 --- a/devcloud/mcenter/apps/policy/impl/permission.go +++ b/devcloud/mcenter/apps/policy/impl/permission.go @@ -51,7 +51,7 @@ func (i *PolicyServiceImpl) QueryEndpoint(ctx context.Context, in *policy.QueryE roleReq := role.NewQueryMatchedEndpointRequest() policies.ForEach(func(t *policy.Policy) { - roleReq.Add(t.RoleId) + roleReq.Add(t.RoleId...) }) if policies.Len() > 0 { diff --git a/devcloud/mcenter/apps/policy/impl/policy.go b/devcloud/mcenter/apps/policy/impl/policy.go index d7ba982..2ff26ea 100644 --- a/devcloud/mcenter/apps/policy/impl/policy.go +++ b/devcloud/mcenter/apps/policy/impl/policy.go @@ -2,6 +2,7 @@ package impl import ( "context" + "slices" "122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace" "122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy" @@ -79,7 +80,7 @@ func (i *PolicyServiceImpl) QueryPolicy(ctx context.Context, in *policy.QueryPol if in.WithRole { roleReq := role.NewQueryRoleRequest() set.ForEach(func(t *policy.Policy) { - roleReq.AddRoleId(t.RoleId) + roleReq.AddRoleId(t.RoleId...) }) roleSet, err := role.GetService().QueryRole(ctx, roleReq) if err != nil { @@ -87,7 +88,7 @@ func (i *PolicyServiceImpl) QueryPolicy(ctx context.Context, in *policy.QueryPol } set.ForEach(func(p *policy.Policy) { p.Role = roleSet.Filter(func(t *role.Role) bool { - return p.RoleId == t.Id + return slices.Contains(p.RoleId, t.Id) }).First() }) } diff --git a/devcloud/mcenter/apps/policy/impl/policy_test.go b/devcloud/mcenter/apps/policy/impl/policy_test.go index aa6abd8..61c78c1 100644 --- a/devcloud/mcenter/apps/policy/impl/policy_test.go +++ b/devcloud/mcenter/apps/policy/impl/policy_test.go @@ -22,7 +22,7 @@ func TestCreatePolicy(t *testing.T) { req := policy.NewCreatePolicyRequest() req.SetNamespaceId(1) req.UserId = 2 - req.RoleId = 1 + req.RoleId = []uint64{1} set, err := impl.CreatePolicy(ctx, req) if err != nil { t.Fatal(err) diff --git a/devcloud/mcenter/apps/policy/model.go b/devcloud/mcenter/apps/policy/model.go index 1deb0dc..917d457 100644 --- a/devcloud/mcenter/apps/policy/model.go +++ b/devcloud/mcenter/apps/policy/model.go @@ -40,24 +40,24 @@ func (p *Policy) String() string { func NewCreatePolicyRequest() *CreatePolicyRequest { return &CreatePolicyRequest{ + ResourceScope: ResourceScope{ + Scope: map[string]string{}, + }, + RoleId: []uint64{}, Extras: map[string]string{}, - Scope: map[string]string{}, Enabled: true, ReadOnly: false, } } type CreatePolicyRequest struct { + ResourceScope // 创建者 CreateBy uint64 `json:"create_by" bson:"create_by" gorm:"column:create_by;type:uint" description:"创建者" optional:"true"` - // 空间 - NamespaceId *uint64 `json:"namespace_id" bson:"namespace_id" gorm:"column:namespace_id;type:varchar(200);index" description:"策略生效的空间Id" optional:"true"` // 用户Id UserId uint64 `json:"user_id" bson:"user_id" gorm:"column:user_id;type:uint;not null;index" validate:"required" description:"被授权的用户"` // 角色Id - RoleId uint64 `json:"role_id" bson:"role_id" gorm:"column:role_id;type:uint;not null;index" validate:"required" description:"被关联的角色"` - // 访问范围, 需要提前定义scope, 比如环境 - Scope map[string]string `json:"scope" bson:"scope" gorm:"column:scope;serializer:json;type:json" description:"数据访问的范围" optional:"true"` + RoleId []uint64 `json:"role_id" bson:"role_id" gorm:"column:role_id;type:json;not null;serializer:json" validate:"required" description:"被关联的角色"` // 策略过期时间 ExpiredTime *time.Time `json:"expired_time" bson:"expired_time" gorm:"column:expired_time;type:timestamp;index" description:"策略过期时间" optional:"true"` // 只读策略, 不允许用户修改, 一般用于系统管理 @@ -70,6 +70,13 @@ type CreatePolicyRequest struct { Extras map[string]string `json:"extras" bson:"extras" gorm:"column:extras;serializer:json;type:json" description:"扩展信息" optional:"true"` } +type ResourceScope struct { + // 空间 + NamespaceId *uint64 `json:"namespace_id" bson:"namespace_id" gorm:"column:namespace_id;type:varchar(200);index" description:"策略生效的空间Id" optional:"true"` + // 访问范围, 需要提前定义scope, 比如环境, 后端开发小组,开发资源 + Scope map[string]string `json:"scope" bson:"scope" gorm:"column:scope;serializer:json;type:json" description:"数据访问的范围" optional:"true"` +} + func (r *CreatePolicyRequest) Validate() error { return validator.Validate(r) } diff --git a/devcloud/mpaas/apps/application/impl/application.go b/devcloud/mpaas/apps/application/impl/application.go new file mode 100644 index 0000000..b201a35 --- /dev/null +++ b/devcloud/mpaas/apps/application/impl/application.go @@ -0,0 +1,71 @@ +package impl + +import ( + "context" + + "122.51.31.227/go-course/go18/devcloud/mpaas/apps/application" + "github.com/infraboard/mcube/v2/ioc/config/datasource" + "github.com/infraboard/mcube/v2/types" +) + +// CreateApplication implements application.Service. +func (i *ApplicationServiceImpl) CreateApplication(ctx context.Context, in *application.CreateApplicationRequest) (*application.Application, error) { + ins, err := application.NewApplication(*in) + if err != nil { + return nil, err + } + + if err := datasource.DBFromCtx(ctx). + Create(ins). + Error; err != nil { + return nil, err + } + return ins, nil +} + +// QueryApplication implements application.Service. +func (i *ApplicationServiceImpl) QueryApplication(ctx context.Context, in *application.QueryApplicationRequest) (*types.Set[*application.Application], error) { + set := types.New[*application.Application]() + + query := datasource.DBFromCtx(ctx).Model(&application.Application{}) + if in.Id != "" { + query = query.Where("id = ?", in.Id) + } + if in.Name != "" { + query = query.Where("name = ?", in.Name) + } + if in.Ready != nil { + query = query.Where("ready = ?", *in.Ready) + } + + err := query.Count(&set.Total).Error + if err != nil { + return nil, err + } + + err = query. + Order("created_at desc"). + Offset(int(in.ComputeOffset())). + Limit(int(in.PageSize)). + Find(&set.Items). + Error + if err != nil { + return nil, err + } + return set, nil +} + +// DescribeApplication implements application.Service. +func (i *ApplicationServiceImpl) DescribeApplication(context.Context, *application.DescribeApplicationRequest) (*application.Application, error) { + panic("unimplemented") +} + +// UpdateApplication implements application.Service. +func (i *ApplicationServiceImpl) UpdateApplication(context.Context, *application.UpdateApplicationRequest) (*application.Application, error) { + panic("unimplemented") +} + +// DeleteApplication implements application.Service. +func (i *ApplicationServiceImpl) DeleteApplication(context.Context, *application.DeleteApplicationRequest) (*application.Application, error) { + panic("unimplemented") +} diff --git a/devcloud/mpaas/apps/application/impl/impl.go b/devcloud/mpaas/apps/application/impl/impl.go new file mode 100644 index 0000000..bc889f3 --- /dev/null +++ b/devcloud/mpaas/apps/application/impl/impl.go @@ -0,0 +1,31 @@ +package impl + +import ( + "122.51.31.227/go-course/go18/devcloud/mpaas/apps/application" + "github.com/infraboard/mcube/v2/ioc" + "github.com/infraboard/mcube/v2/ioc/config/datasource" +) + +func init() { + ioc.Controller().Registry(&ApplicationServiceImpl{}) +} + +var _ application.Service = (*ApplicationServiceImpl)(nil) + +type ApplicationServiceImpl struct { + ioc.ObjectImpl +} + +func (i *ApplicationServiceImpl) Init() error { + if datasource.Get().AutoMigrate { + err := datasource.DB().AutoMigrate(&application.Application{}) + if err != nil { + return err + } + } + return nil +} + +func (i *ApplicationServiceImpl) Name() string { + return application.APP_NAME +} diff --git a/devcloud/mpaas/apps/application/interface.go b/devcloud/mpaas/apps/application/interface.go index 23280a8..5ef6980 100644 --- a/devcloud/mpaas/apps/application/interface.go +++ b/devcloud/mpaas/apps/application/interface.go @@ -3,29 +3,46 @@ package application import ( "context" + "122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy" + "github.com/infraboard/mcube/v2/http/request" + "github.com/infraboard/mcube/v2/ioc" "github.com/infraboard/mcube/v2/types" ) +const ( + APP_NAME = "application" +) + +func GetService() Service { + return ioc.Controller().Get(APP_NAME).(Service) +} + type Service interface { // 创建应用 - CreateApplication(context.Context, CreateApplicationRequest) (*Application, error) + CreateApplication(context.Context, *CreateApplicationRequest) (*Application, error) // 查询应用 - QueryApplication(context.Context, QueryApplicationRequest) (*types.Set[*Application], error) + QueryApplication(context.Context, *QueryApplicationRequest) (*types.Set[*Application], error) // 更新应用 - UpdateApplication(context.Context, UpdateApplicationRequest) (*Application, error) + UpdateApplication(context.Context, *UpdateApplicationRequest) (*Application, error) // 删除应用 - DeleteApplication(context.Context, DeleteApplicationRequest) (*Application, error) + DeleteApplication(context.Context, *DeleteApplicationRequest) (*Application, error) // 获取应用 - DescribeApplication(context.Context, DescribeApplicationRequest) (*Application, error) + DescribeApplication(context.Context, *DescribeApplicationRequest) (*Application, error) } type QueryApplicationRequest struct { + *request.PageRequest + policy.ResourceScope + QueryApplicationRequestSpec +} + +type QueryApplicationRequestSpec struct { // 应用ID Id string `json:"id" bson:"_id"` // 应用名称 Name string `json:"name" bson:"name"` - // 应用状态 - Status string `json:"status" bson:"status"` + // 应用是否就绪 + Ready *bool `json:"ready" bson:"ready"` } type UpdateApplicationRequest struct { diff --git a/devcloud/mpaas/apps/application/model.go b/devcloud/mpaas/apps/application/model.go index d194f00..b164d55 100644 --- a/devcloud/mpaas/apps/application/model.go +++ b/devcloud/mpaas/apps/application/model.go @@ -1,92 +1,189 @@ package application import ( + "bytes" "time" + "github.com/google/uuid" + "github.com/infraboard/mcube/v2/ioc/config/validator" "github.com/infraboard/mcube/v2/tools/pretty" ) +func NewApplication(req CreateApplicationRequest) (*Application, error) { + app := &Application{ + CreateApplicationRequest: req, + } + + if err := app.Validate(); err != nil { + return nil, err + } + + // 动态计算评审状态 + if len(req.Audits) > 0 { + app.SetReady(true) + } else { + app.SetReady(false) + } + + app.BuildId() + return app, nil +} + type Application struct { // 对象Id - Id string `json:"id" bson:"_id"` + Id string `json:"id" bson:"_id" gorm:"column:id;primary_key"` // 更新时间 - UpdateAt time.Time `json:"update_at" bson:"update_at"` + UpdateAt time.Time `json:"update_at" bson:"update_at" gorm:"column:update_at"` // 更新人 - UpdateBy string `json:"update_by" bson:"update_by"` + UpdateBy string `json:"update_by" bson:"update_by" gorm:"column:update_by"` // 创建请求 CreateApplicationRequest + // 应用状态 + ApplicationStatus +} + +func (a *Application) TableName() string { + return "applications" } func (a *Application) String() string { return pretty.ToJSON(a) } +func (a *Application) SetReady(v bool) *Application { + a.Ready = &v + return a +} + +func (a *Application) BuildId() { + bf := bytes.NewBuffer([]byte{}) + + switch a.Type { + case TYPE_SOURCE_CODE: + bf.WriteString(a.CodeRepository.SshUrl) + case TYPE_CONTAINER_IMAGE: + bf.WriteString(a.GetImageRepositoryPrimaryAddress()) + } + + a.Id = uuid.NewSHA1(uuid.Nil, bf.Bytes()).String() +} + +func NewCreateApplicationRequest() *CreateApplicationRequest { + return &CreateApplicationRequest{ + CreateApplicationSpec: CreateApplicationSpec{ + Labels: map[string]string{}, + Extras: map[string]string{}, + ImageRepository: []ImageRepository{}, + }, + } +} + type CreateApplicationRequest struct { // 创建人 - CreateBy string `json:"create_by" bson:"create_by" description:"创建人"` + CreateBy string `json:"create_by" bson:"create_by" gorm:"column:create_by" description:"创建人"` // 创建时间 - CreateAt time.Time `json:"create_at" bson:"create_at"` + CreateAt time.Time `json:"create_at" bson:"create_at" gorm:"column:create_at" description:"创建时间"` // 应用所属空间名称 - Namespace string `json:"namespace" bson:"namespace" description:"应用所属空间名称"` + Namespace string `json:"namespace" bson:"namespace" description:"应用所属空间名称" gorm:"column:namespace"` // 应用创建参数 CreateApplicationSpec } +func (a *CreateApplicationRequest) Validate() error { + return validator.Validate(a) +} + +func (a *CreateApplicationRequest) GetImageRepositoryPrimaryAddress() string { + for _, repo := range a.ImageRepository { + if repo.IsPrimary { + return repo.Address + } + } + + return "" +} + type CreateApplicationSpec struct { - // 该应用是否已经准备就绪 - Ready bool `json:"ready" bson:"ready" description:"该应用是否已经准备就绪"` // 应用名称 - Name string `json:"name" bson:"name" description:"应用名称"` + Name string `json:"name" bson:"name" gorm:"column:name" description:"应用名称"` // 应用描述 - Description string `json:"description" bson:"description" description:"应用描述"` + Description string `json:"description" bson:"description" gorm:"column:description" description:"应用描述"` // 应用图标 - Icon string `json:"icon" bson:"icon" description:"应用图标"` + Icon string `json:"icon" bson:"icon" gorm:"column:icons" description:"应用图标"` // 应用类型 - Type TYPE `json:"type" bson:"type" description:"应用类型, SOURCE_CODE, CONTAINER_IMAGE, OTHER"` + Type TYPE `json:"type" bson:"type" gorm:"column:type" description:"应用类型, SOURCE_CODE, CONTAINER_IMAGE, OTHER"` // 应用代码仓库信息 - CodeRepository CodeRepository `json:"code_repository" bson:"code_repository" description:"应用代码仓库信息"` + CodeRepository CodeRepository `json:"code_repository" bson:",inline" gorm:"embedded" description:"应用代码仓库信息"` // 应用镜像仓库信息 - ImageRepository ImageRepository `json:"image_repository" bson:"image_repository" description:"应用镜像仓库信息"` + ImageRepository []ImageRepository `json:"image_repository" gorm:"column:image_repository;serializer:json;" bson:"image_repository" description:"应用镜像仓库信息"` // 应用所有者 - Owner string `json:"owner" bson:"owner" description:"应用所有者"` + Owner string `json:"owner" bson:"owner" gorm:"column:owner" description:"应用所有者"` // 应用等级, 评估这个应用的重要程度 - Level uint32 `json:"level" bson:"level" description:"应用等级, 评估这个应用的重要程度"` + Level *uint32 `json:"level" bson:"level" gorm:"column:level" description:"应用等级, 评估这个应用的重要程度"` // 应用优先级, 应用启动的先后顺序 - Priority uint32 `json:"priority" bson:"priority" description:"应用优先级, 应用启动的先后顺序"` + Priority *uint32 `json:"priority" bson:"priority" gorm:"column:priority" description:"应用优先级, 应用启动的先后顺序"` // 应用标签 - Labels map[string]string `json:"labels" bson:"labels" description:"应用标签"` + Labels map[string]string `json:"labels" bson:"labels" gorm:"column:labels;serializer:json" description:"应用标签"` + // 额外的其他属性 + Extras map[string]string `json:"extras" form:"extras" bson:"extras" gorm:"column:extras;serializer:json;"` + + // 指定应用的评审方 + Audits []ApplicationReadyAudit `json:"audits" bson:"audits" gorm:"column:audits;serializer:json" description:"参与应用准备就绪的评审方"` } // 服务代码仓库信息 type CodeRepository struct { // 仓库提供商 - Provider SCM_PROVIDER `json:"provider" bson:"provider"` + Provider SCM_PROVIDER `json:"provider" bson:"provider" gorm:"column:provider"` // token 操作仓库, 比如设置回调 - Token string `json:"token" bson:"token"` + Token string `json:"token" bson:"token" gorm:"column:token"` // 仓库对应的项目Id - ProjectId string `json:"project_id" bson:"project_id"` + ProjectId string `json:"project_id" bson:"project_id" gorm:"column:project_id"` // 仓库对应空间 - Namespace string `json:"namespace" bson:"namespace"` + Namespace string `json:"namespace" bson:"namespace" gorm:"column:namespace"` // 仓库web url地址 - WebUrl string `json:"web_url" bson:"web_url"` + WebUrl string `json:"web_url" bson:"web_url" gorm:"column:web_url"` // 仓库ssh url地址 - SshUrl string `json:"ssh_url" bson:"ssh_url"` + SshUrl string `json:"ssh_url" bson:"ssh_url" gorm:"column:ssh_url"` // 仓库http url地址 - HttpUrl string `json:"http_url" bson:"http_url"` + HttpUrl string `json:"http_url" bson:"http_url" gorm:"column:http_url"` // 源代码使用的编程语言, 构建时, 不同语言有不同的构建环境 - Language *LANGUAGE `json:"language" bson:"language"` + Language *LANGUAGE `json:"language" bson:"language" gorm:"column:language"` // 开启Hook设置 - EnableHook bool `json:"enable_hook" bson:"enable_hook"` + EnableHook bool `json:"enable_hook" bson:"enable_hook" gorm:"column:enable_hook"` // Hook设置 - HookConfig string `json:"hook_config" bson:"hook_config"` + HookConfig string `json:"hook_config" bson:"hook_config" gorm:"column:hook_config"` // scm设置Hook后返回的id, 用于删除应用时,取消hook使用 - HookId string `json:"hook_id" bson:"hook_id"` + HookId string `json:"hook_id" bson:"hook_id" gorm:"column:hook_id"` // 仓库的创建时间 - CreatedAt time.Time `json:"created_at" bson:"created_at"` + CreatedAt time.Time `json:"created_at" bson:"created_at" gorm:"column:created_at"` } // 镜像仓库 type ImageRepository struct { - // 服务镜像地址 + // 服务镜像地址, 比如 gcr.lank8s.cn/kaniko-project/executor Address string `json:"address" bson:"address"` + // 是不是主仓库 + IsPrimary bool `json:"is_primary" bson:"is_primary"` +} + +type ApplicationStatus struct { + // 该应用是否已经准备就绪,多方确认的一个过程后计算出来的 + Ready *bool `json:"ready" bson:"ready" gorm:"column:ready" description:"该应用是否已经准备就绪"` + // 就绪状态修改时间 + UpdateAt time.Time `json:"ready_update_at" bson:"ready_update_at" gorm:"column:ready_update_at" description:"就绪状态修改时间"` +} + +// 参与应用准备就绪的评审方 +type ApplicationReadyAudit struct { + // 评审角色, Dev, Test, Ops + RoleName string `json:"role_name"` + // 评审人 + AuditBy string `json:"audit_by"` + // 评审时间 + AuditAt time.Time `json:"audit_at"` + // 是否就绪 + Ready bool `json:"ready"` + // 评审建议 + Message string `json:"message"` } diff --git a/devcloud/mpaas/apps/registry.go b/devcloud/mpaas/apps/registry.go index cff2ab9..a53a700 100644 --- a/devcloud/mpaas/apps/registry.go +++ b/devcloud/mpaas/apps/registry.go @@ -1 +1,5 @@ package apps + +import ( + _ "122.51.31.227/go-course/go18/devcloud/mpaas/apps/application/impl" +) diff --git a/devcloud/mpaas/design.drawio b/devcloud/mpaas/design.drawio new file mode 100644 index 0000000..99bc0af --- /dev/null +++ b/devcloud/mpaas/design.drawio @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/devcloud/mpaas/registry.go b/devcloud/mpaas/registry.go new file mode 100644 index 0000000..2a1bce0 --- /dev/null +++ b/devcloud/mpaas/registry.go @@ -0,0 +1 @@ +package mpaas