Feat: add RBAC support (#3493)

* Feat: add the rbac data model

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Feat: add some api about the project

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Feat: add CRUD about the project and the project user

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Feat: add CRUD about the role and perm check filter function

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Feat: update swagger config

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Feat: add default roles and perm policies

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Feat: add perm check filter for all webservice

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Feat: change the method that find project name

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Feat: query applications and envs by user perm

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Feat: support get login user info

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: change default permissions

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Feat: change PermPolicy to Permission

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Feat: add some unit test and fix the e2e test error

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: change some comment word

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: e2e api path error

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
This commit is contained in:
barnettZQG
2022-03-28 16:03:11 +08:00
committed by GitHub
parent 795231ceb5
commit 044c4bf73c
69 changed files with 5026 additions and 932 deletions
+14 -1
View File
@@ -112,9 +112,22 @@ type FuzzyQueryOption struct {
Query string
}
// InQueryOption defines the include search filter option
type InQueryOption struct {
Key string
Values []string
}
// IsNotExistQueryOption means the value is empty
type IsNotExistQueryOption struct {
Key string
}
// FilterOptions filter query returned items
type FilterOptions struct {
Queries []FuzzyQueryOption
Queries []FuzzyQueryOption
In []InQueryOption
IsNotExist []IsNotExistQueryOption
}
// ListOptions list api options
+44 -9
View File
@@ -120,23 +120,23 @@ func (m *kubeapi) Add(ctx context.Context, entity datastore.Entity) error {
}
// BatchAdd batch add entity, this operation has some atomicity.
func (m *kubeapi) BatchAdd(ctx context.Context, entitys []datastore.Entity) error {
donotRollback := make(map[string]int)
for i, saveEntity := range entitys {
func (m *kubeapi) BatchAdd(ctx context.Context, entities []datastore.Entity) error {
notRollback := make(map[string]int)
for i, saveEntity := range entities {
if err := m.Add(ctx, saveEntity); err != nil {
if errors.Is(err, datastore.ErrRecordExist) {
donotRollback[saveEntity.PrimaryKey()] = 1
notRollback[saveEntity.PrimaryKey()] = 1
}
for _, deleteEntity := range entitys[:i] {
if _, exit := donotRollback[deleteEntity.PrimaryKey()]; !exit {
for _, deleteEntity := range entities[:i] {
if _, exit := notRollback[deleteEntity.PrimaryKey()]; !exit {
if err := m.Delete(ctx, deleteEntity); err != nil {
if !errors.Is(err, datastore.ErrRecordNotExist) {
log.Logger.Errorf("rollback delete component failure %w", err)
log.Logger.Errorf("rollback delete entity failure %w", err)
}
}
}
}
return datastore.NewDBError(fmt.Errorf("save components occur error, %w", err))
return datastore.NewDBError(fmt.Errorf("save entities occur error, %w", err))
}
}
return nil
@@ -347,6 +347,24 @@ func (m *kubeapi) List(ctx context.Context, entity datastore.Entity, op *datasto
}
selector = selector.Add(*rq)
}
if op != nil {
for _, inFilter := range op.In {
rq, err := labels.NewRequirement(inFilter.Key, selection.In, inFilter.Values)
if err != nil {
log.Logger.Errorf("new list requirement failure %s", err.Error())
return nil, datastore.ErrIndexInvalid
}
selector = selector.Add(*rq)
}
for _, notFilter := range op.IsNotExist {
rq, err := labels.NewRequirement(notFilter.Key, selection.DoesNotExist, []string{})
if err != nil {
log.Logger.Errorf("new list requirement failure %s", err.Error())
return nil, datastore.ErrIndexInvalid
}
selector = selector.Add(*rq)
}
}
options := &client.ListOptions{
LabelSelector: selector,
Namespace: m.namespace,
@@ -385,7 +403,6 @@ func (m *kubeapi) List(ctx context.Context, entity datastore.Entity, op *datasto
items = items[:limit]
}
var list []datastore.Entity
log.Logger.Debugf("query %s result count %d", selector, len(items))
for _, item := range items {
ent, err := datastore.NewEntity(entity)
if err != nil {
@@ -416,6 +433,24 @@ func (m *kubeapi) Count(ctx context.Context, entity datastore.Entity, filterOpti
}
selector = selector.Add(*rq)
}
if filterOptions != nil {
for _, inFilter := range filterOptions.In {
rq, err := labels.NewRequirement(inFilter.Key, selection.In, inFilter.Values)
if err != nil {
return 0, datastore.ErrIndexInvalid
}
selector = selector.Add(*rq)
}
for _, notFilter := range filterOptions.IsNotExist {
rq, err := labels.NewRequirement(notFilter.Key, selection.DoesNotExist, []string{})
if err != nil {
log.Logger.Errorf("new list requirement failure %s", err.Error())
return 0, datastore.ErrIndexInvalid
}
selector = selector.Add(*rq)
}
}
options := &client.ListOptions{
LabelSelector: selector,
Namespace: m.namespace,
@@ -96,7 +96,7 @@ var _ = Describe("Test kubeapi datastore driver", func() {
var datas = []datastore.Entity{
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
&model.Application{Name: "kubevela-app-3", Description: "this is demo 3"},
&model.Application{Name: "kubevela-app-4", Description: "this is demo 4"},
&model.Application{Name: "kubevela-app-4", Project: "testProject", Description: "this is demo 4"},
}
err := kubeStore.BatchAdd(context.TODO(), datas)
Expect(err).ToNot(HaveOccurred())
@@ -106,7 +106,7 @@ var _ = Describe("Test kubeapi datastore driver", func() {
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
}
err = kubeStore.BatchAdd(context.TODO(), datas2)
equal := cmp.Diff(strings.Contains(err.Error(), "save components occur error"), true)
equal := cmp.Diff(strings.Contains(err.Error(), "save entities occur error"), true)
Expect(equal).To(BeEmpty())
})
@@ -157,6 +157,26 @@ var _ = Describe("Test kubeapi datastore driver", func() {
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 4)
Expect(diff).Should(BeEmpty())
list, err = kubeStore.List(context.TODO(), &app, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{In: []datastore.InQueryOption{
{
Key: "name",
Values: []string{"kubevela-app-3", "kubevela-app-2"},
},
}}})
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 2)
Expect(diff).Should(BeEmpty())
list, err = kubeStore.List(context.TODO(), &app, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{IsNotExist: []datastore.IsNotExistQueryOption{
{
Key: "project",
},
}}})
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 3)
Expect(diff).Should(BeEmpty())
})
It("Test list clusters with sort and fuzzy query", func() {
@@ -221,6 +241,23 @@ var _ = Describe("Test kubeapi datastore driver", func() {
})
Expect(err).Should(Succeed())
Expect(count).Should(Equal(int64(2)))
count, err = kubeStore.Count(context.TODO(), &app, &datastore.FilterOptions{In: []datastore.InQueryOption{
{
Key: "name",
Values: []string{"kubevela-app-3", "kubevela-app-2"},
},
}})
Expect(err).ShouldNot(HaveOccurred())
Expect(count).Should(Equal(int64(2)))
count, err = kubeStore.Count(context.TODO(), &app, &datastore.FilterOptions{IsNotExist: []datastore.IsNotExistQueryOption{
{
Key: "project",
},
}})
Expect(err).ShouldNot(HaveOccurred())
Expect(count).Should(Equal(int64(3)))
})
It("Test isExist function", func() {
+18 -14
View File
@@ -84,23 +84,23 @@ func (m *mongodb) Add(ctx context.Context, entity datastore.Entity) error {
}
// BatchAdd batch add entity, this operation has some atomicity.
func (m *mongodb) BatchAdd(ctx context.Context, entitys []datastore.Entity) error {
donotRollback := make(map[string]int)
for i, saveEntity := range entitys {
func (m *mongodb) BatchAdd(ctx context.Context, entities []datastore.Entity) error {
notRollback := make(map[string]int)
for i, saveEntity := range entities {
if err := m.Add(ctx, saveEntity); err != nil {
if errors.Is(err, datastore.ErrRecordExist) {
donotRollback[saveEntity.PrimaryKey()] = 1
notRollback[saveEntity.PrimaryKey()] = 1
}
for _, deleteEntity := range entitys[:i] {
if _, exit := donotRollback[deleteEntity.PrimaryKey()]; !exit {
for _, deleteEntity := range entities[:i] {
if _, exit := notRollback[deleteEntity.PrimaryKey()]; !exit {
if err := m.Delete(ctx, deleteEntity); err != nil {
if !errors.Is(err, datastore.ErrRecordNotExist) {
log.Logger.Errorf("rollback delete component failure %w", err)
log.Logger.Errorf("rollback delete entity failure %w", err)
}
}
}
}
return datastore.NewDBError(fmt.Errorf("save components occur error, %w", err))
return datastore.NewDBError(fmt.Errorf("save entities occur error, %w", err))
}
}
return nil
@@ -192,10 +192,14 @@ func (m *mongodb) Delete(ctx context.Context, entity datastore.Entity) error {
}
func _applyFilterOptions(filter bson.D, filterOptions datastore.FilterOptions) bson.D {
if len(filterOptions.Queries) > 0 {
for _, queryOp := range filterOptions.Queries {
filter = append(filter, bson.E{Key: strings.ToLower(queryOp.Key), Value: bsonx.Regex(".*"+queryOp.Query+".*", "s")})
}
for _, queryOp := range filterOptions.Queries {
filter = append(filter, bson.E{Key: strings.ToLower(queryOp.Key), Value: bsonx.Regex(".*"+queryOp.Query+".*", "s")})
}
for _, queryOp := range filterOptions.In {
filter = append(filter, bson.E{Key: strings.ToLower(queryOp.Key), Value: bson.D{bson.E{Key: "$in", Value: queryOp.Values}}})
}
for _, queryOp := range filterOptions.IsNotExist {
filter = append(filter, bson.E{Key: strings.ToLower(queryOp.Key), Value: bson.D{bson.E{Key: "$eq", Value: ""}}})
}
return filter
}
@@ -216,7 +220,7 @@ func (m *mongodb) List(ctx context.Context, entity datastore.Entity, op *datasto
})
}
}
if op != nil && len(op.Queries) > 0 {
if op != nil {
filter = _applyFilterOptions(filter, op.FilterOptions)
}
var findOptions options.FindOptions
@@ -276,7 +280,7 @@ func (m *mongodb) Count(ctx context.Context, entity datastore.Entity, filterOpti
})
}
}
if filterOptions != nil && len(filterOptions.Queries) > 0 {
if filterOptions != nil {
filter = _applyFilterOptions(filter, *filterOptions)
}
count, err := collection.CountDocuments(ctx, filter)
@@ -70,7 +70,7 @@ var _ = Describe("Test mongodb datastore driver", func() {
var datas = []datastore.Entity{
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
&model.Application{Name: "kubevela-app-3", Description: "this is demo 3"},
&model.Application{Name: "kubevela-app-4", Description: "this is demo 4"},
&model.Application{Name: "kubevela-app-4", Project: "test-project", Description: "this is demo 4"},
&model.Workflow{Name: "kubevela-app-workflow", AppPrimaryKey: "kubevela-app-2", Description: "this is workflow"},
&model.ApplicationTrigger{Name: "kubevela-app-trigger", AppPrimaryKey: "kubevela-app-2", Token: "token-test", Description: "this is demo 4"},
}
@@ -82,7 +82,7 @@ var _ = Describe("Test mongodb datastore driver", func() {
&model.Application{Name: "kubevela-app-2", Description: "this is demo 2"},
}
err = mongodbDriver.BatchAdd(context.TODO(), datas2)
equal := cmp.Diff(strings.Contains(err.Error(), "save components occur error"), true)
equal := cmp.Diff(strings.Contains(err.Error(), "save entities occur error"), true)
Expect(equal).To(BeEmpty())
})
@@ -133,6 +133,25 @@ var _ = Describe("Test mongodb datastore driver", func() {
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 1)
Expect(diff).Should(BeEmpty())
list, err = mongodbDriver.List(context.TODO(), &app, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{In: []datastore.InQueryOption{
{
Key: "name",
Values: []string{"kubevela-app-3", "kubevela-app-2"},
},
}}})
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 2)
Expect(diff).Should(BeEmpty())
list, err = mongodbDriver.List(context.TODO(), &app, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{IsNotExist: []datastore.IsNotExistQueryOption{
{
Key: "project",
},
}}})
Expect(err).ShouldNot(HaveOccurred())
diff = cmp.Diff(len(list), 3)
Expect(diff).Should(BeEmpty())
})
It("Test list clusters with sort and fuzzy query", func() {
@@ -196,6 +215,23 @@ var _ = Describe("Test mongodb datastore driver", func() {
})
Expect(err).Should(Succeed())
Expect(count).Should(Equal(int64(2)))
count, err = mongodbDriver.Count(context.TODO(), &app, &datastore.FilterOptions{In: []datastore.InQueryOption{
{
Key: "name",
Values: []string{"kubevela-app-3", "kubevela-app-2"},
},
}})
Expect(err).ShouldNot(HaveOccurred())
Expect(count).Should(Equal(int64(2)))
count, err = mongodbDriver.Count(context.TODO(), &app, &datastore.FilterOptions{IsNotExist: []datastore.IsNotExistQueryOption{
{
Key: "project",
},
}})
Expect(err).ShouldNot(HaveOccurred())
Expect(count).Should(Equal(int64(3)))
})
It("Test isExist function", func() {
+4
View File
@@ -25,6 +25,7 @@ type Project struct {
BaseModel
Name string `json:"name"`
Alias string `json:"alias"`
Owner string `json:"owner"`
Description string `json:"description,omitempty"`
}
@@ -49,5 +50,8 @@ func (p *Project) Index() map[string]string {
if p.Name != "" {
index["name"] = p.Name
}
if p.Owner != "" {
index["owner"] = p.Owner
}
return index
}
+4
View File
@@ -26,6 +26,7 @@ type Target struct {
BaseModel
Name string `json:"name"`
Alias string `json:"alias,omitempty"`
Project string `json:"project"`
Description string `json:"description,omitempty"`
Cluster *ClusterTarget `json:"cluster,omitempty"`
Variable map[string]interface{} `json:"variable,omitempty"`
@@ -52,6 +53,9 @@ func (d *Target) Index() map[string]string {
if d.Name != "" {
index["name"] = d.Name
}
if d.Project != "" {
index["project"] = d.Project
}
return index
}
+149 -3
View File
@@ -27,8 +27,14 @@ import (
func init() {
RegisterModel(&User{})
RegisterModel(&ProjectUser{})
RegisterModel(&Role{})
RegisterModel(&Permission{})
RegisterModel(&PermissionTemplate{})
}
// DefaultAdminUserName default admin user name
const DefaultAdminUserName = "admin"
// User is the model of user
type User struct {
BaseModel
@@ -38,6 +44,8 @@ type User struct {
Password string `json:"password,omitempty"`
Disabled bool `json:"disabled"`
LastLoginTime time.Time `json:"lastLoginTime,omitempty"`
// UserRoles binding the platform level roles
UserRoles []string `json:"userRoles"`
}
// TableName return custom table name
@@ -70,9 +78,10 @@ func (u *User) Index() map[string]string {
// ProjectUser is the model of user in project
type ProjectUser struct {
BaseModel
Username string `json:"username"`
ProjectName string `json:"projectName"`
UserRoles []string `json:"userRoles"`
Username string `json:"username"`
ProjectName string `json:"projectName"`
// UserRoles binding the project level roles
UserRoles []string `json:"userRoles"`
}
// TableName return custom table name
@@ -114,3 +123,140 @@ type CustomClaims struct {
GrantType string `json:"grantType"`
jwt.StandardClaims
}
// Role is a model for a new RBAC mode.
type Role struct {
BaseModel
Name string `json:"name"`
Alias string `json:"alias"`
Project string `json:"project,omitempty"`
Permissions []string `json:"permissions"`
}
// Permission is a model for a new RBAC mode.
type Permission struct {
BaseModel
Name string `json:"name"`
Alias string `json:"alias"`
Project string `json:"project,omitempty"`
Resources []string `json:"resources"`
Actions []string `json:"actions"`
// Effect option values: Allow,Deny
Effect string `json:"effect"`
Principal *Principal `json:"principal,omitempty"`
Condition *Condition `json:"condition,omitempty"`
}
// Principal is a model for a new RBAC mode.
type Principal struct {
// Type options: User or Role
Type string `json:"type"`
Names []string `json:"names"`
}
// Condition is a model for a new RBAC mode.
type Condition struct {
}
// TableName return custom table name
func (r *Role) TableName() string {
return tableNamePrefix + "role"
}
// ShortTableName return custom table name
func (r *Role) ShortTableName() string {
return "role"
}
// PrimaryKey return custom primary key
func (r *Role) PrimaryKey() string {
if r.Project == "" {
return r.Name
}
return fmt.Sprintf("%s-%s", r.Project, r.Name)
}
// Index return custom index
func (r *Role) Index() map[string]string {
index := make(map[string]string)
if r.Name != "" {
index["name"] = r.Name
}
if r.Project != "" {
index["project"] = r.Project
}
return index
}
// TableName return custom table name
func (p *Permission) TableName() string {
return tableNamePrefix + "perm"
}
// ShortTableName return custom table name
func (p *Permission) ShortTableName() string {
return "perm"
}
// PrimaryKey return custom primary key
func (p *Permission) PrimaryKey() string {
if p.Project == "" {
return p.Name
}
return fmt.Sprintf("%s-%s", p.Project, p.Name)
}
// Index return custom index
func (p *Permission) Index() map[string]string {
index := make(map[string]string)
if p.Name != "" {
index["name"] = p.Name
}
if p.Project != "" {
index["project"] = p.Project
}
if p.Principal != nil && p.Principal.Type != "" {
index["principal.type"] = p.Principal.Type
}
return index
}
// PermissionTemplate is a model for a new RBAC mode.
type PermissionTemplate struct {
BaseModel
Name string `json:"name"`
Alias string `json:"alias"`
// Scope options: project or platform
Scope string `json:"scope"`
Resources []string `json:"resources"`
Actions []string `json:"actions"`
Effect string `json:"effect"`
Condition *Condition `json:"condition,omitempty"`
}
// TableName return custom table name
func (p *PermissionTemplate) TableName() string {
return tableNamePrefix + "perm_temp"
}
// ShortTableName return custom table name
func (p *PermissionTemplate) ShortTableName() string {
return "perm_temp"
}
// PrimaryKey return custom primary key
func (p *PermissionTemplate) PrimaryKey() string {
return p.Name
}
// Index return custom index
func (p *PermissionTemplate) Index() map[string]string {
index := make(map[string]string)
if p.Name != "" {
index["name"] = p.Name
}
if p.Scope != "" {
index["scope"] = p.Scope
}
return index
}
+113 -15
View File
@@ -289,10 +289,10 @@ type ClusterBase struct {
// ListApplicationOptions list application query options
type ListApplicationOptions struct {
Project string `json:"project"`
Env string `json:"env"`
TargetName string `json:"targetName"`
Query string `json:"query"`
Projects []string `json:"projects"`
Env string `json:"env"`
TargetName string `json:"targetName"`
Query string `json:"query"`
}
// ListApplicationResponse list applications by query params
@@ -678,6 +678,7 @@ type ProjectBase struct {
Description string `json:"description"`
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
Owner NameAlias `json:"owner,omitempty"`
}
// CreateProjectRequest create project request body
@@ -685,6 +686,14 @@ type CreateProjectRequest struct {
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Description string `json:"description" optional:"true"`
Owner string `json:"owner" optional:"true"`
}
// UpdateProjectRequest update a project request body
type UpdateProjectRequest struct {
Alias string `json:"alias" validate:"checkalias" optional:"true"`
Description string `json:"description" optional:"true"`
Owner string `json:"owner" optional:"true"`
}
// Env models the data of env in API
@@ -989,6 +998,7 @@ type ApplicationTrait struct {
type CreateTargetRequest struct {
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias,omitempty" validate:"checkalias" optional:"true"`
Project string `json:"project" validate:"checkname"`
Description string `json:"description,omitempty" optional:"true"`
Cluster *ClusterTarget `json:"cluster,omitempty"`
Variable map[string]interface{} `json:"variable,omitempty"`
@@ -1029,6 +1039,7 @@ type TargetBase struct {
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
AppNum int64 `json:"appNum,omitempty"`
Project NameAlias `json:"project"`
}
// ApplicationRevisionBase application revision base spec
@@ -1118,29 +1129,39 @@ type DexConfigResponse struct {
// DetailUserResponse is the response of user detail
type DetailUserResponse struct {
UserBase
Projects []ProjectUserBase `json:"projects"`
Projects []*ProjectBase `json:"projects"`
Roles []NameAlias `json:"roles"`
}
// ProjectUserBase project user base
type ProjectUserBase struct {
Name string `json:"name"`
Alias string `json:"alias"`
UserRoles []string `json:"userRoles"`
UserName string `json:"name"`
UserRoles []string `json:"userRoles"`
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
}
// ListProjectUsersResponse the response body that list users belong to a project
type ListProjectUsersResponse struct {
Users []*ProjectUserBase `json:"users"`
Total int64 `json:"total"`
}
// CreateUserRequest create user request
type CreateUserRequest struct {
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias,omitempty" validate:"checkalias" optional:"true"`
Email string `json:"email" validate:"checkemail"`
Password string `json:"password" validate:"checkpassword"`
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias,omitempty" validate:"checkalias" optional:"true"`
Email string `json:"email" validate:"checkemail"`
Password string `json:"password" validate:"checkpassword"`
Roles []string `json:"roles"`
}
// UpdateUserRequest update user request
type UpdateUserRequest struct {
Alias string `json:"alias,omitempty" optional:"true"`
Password string `json:"password,omitempty" validate:"checkpassword" optional:"true"`
Email string `json:"email,omitempty" validate:"checkemail" optional:"true"`
Alias string `json:"alias,omitempty" optional:"true"`
Password string `json:"password,omitempty" validate:"checkpassword" optional:"true"`
Email string `json:"email,omitempty" validate:"checkemail" optional:"true"`
Roles *[]string `json:"roles"`
}
// ListUserResponse list user response
@@ -1170,3 +1191,80 @@ type ListUserOptions struct {
type GetLoginTypeResponse struct {
LoginType string `json:"loginType"`
}
// AddProjectUserRequest the request body that add user to project
type AddProjectUserRequest struct {
UserName string `json:"userName" validate:"checkname"`
UserRoles []string `json:"userRoles"`
}
// UpdateProjectUserRequest the request body that update user role in a project
type UpdateProjectUserRequest struct {
UserRoles []string `json:"userRoles"`
}
// CreateRoleRequest the request body that create a role
type CreateRoleRequest struct {
Name string `json:"name" validate:"checkname"`
Alias string `json:"alias" validate:"checkalias"`
Permissions []string `json:"permissions"`
}
// UpdateRoleRequest the request body that update a role
type UpdateRoleRequest struct {
Alias string `json:"alias" validate:"checkalias"`
Permissions []string `json:"permissions"`
}
// RoleBase the base struct of role
type RoleBase struct {
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
Name string `json:"name"`
Alias string `json:"alias,omitempty"`
Permissions []NameAlias `json:"permissions"`
}
// ListRolesResponse the response body of list roles
type ListRolesResponse struct {
Total int64 `json:"total"`
Roles []*RoleBase `json:"roles"`
}
// PermissionTemplateBase the perm policy template base struct
type PermissionTemplateBase struct {
Name string `json:"name"`
Alias string `json:"alias"`
Resources []string `json:"resources"`
Actions []string `json:"actions"`
Effect string `json:"effect"`
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
}
// PermissionBase the perm policy base struct
type PermissionBase struct {
Name string `json:"name"`
Alias string `json:"alias"`
Resources []string `json:"resources"`
Actions []string `json:"actions"`
Effect string `json:"effect"`
CreateTime time.Time `json:"createTime"`
UpdateTime time.Time `json:"updateTime"`
}
// UpdatePermissionRequest the request body that update permission policy
type UpdatePermissionRequest struct {
Alias string `json:"alias" validate:"checkalias"`
Resources []string `json:"resources"`
Actions []string `json:"actions"`
Effect string `json:"effect" validate:"oneof=Allow Deny"`
}
// LoginUserInfoResponse the response body of login user info
type LoginUserInfoResponse struct {
UserBase
Projects []*ProjectBase `json:"projects"`
PlatformPermissions []PermissionBase `json:"platformPermissions"`
ProjectPermissions map[string][]PermissionBase `json:"projectPermissions"`
}
+10 -70
View File
@@ -18,7 +18,6 @@ package rest
import (
"context"
"errors"
"fmt"
"net/http"
"os"
@@ -27,22 +26,16 @@ import (
restfulspec "github.com/emicklei/go-restful-openapi/v2"
"github.com/emicklei/go-restful/v3"
"github.com/go-openapi/spec"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8stypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/leaderelection"
"k8s.io/client-go/tools/leaderelection/resourcelock"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore/kubeapi"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore/mongodb"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/webservice"
@@ -78,13 +71,15 @@ type leaderConfig struct {
// APIServer interface for call api server
type APIServer interface {
Run(context.Context) error
RegisterServices() restfulspec.Config
RegisterServices(ctx context.Context, initDatabase bool) restfulspec.Config
}
type restServer struct {
webContainer *restful.Container
cfg Config
dataStore datastore.DataStore
// usecases, we register part of the usecase instances
usecases map[string]interface{}
}
// New create restserver with config data
@@ -114,11 +109,7 @@ func New(cfg Config) (a APIServer, err error) {
}
func (s *restServer) Run(ctx context.Context) error {
s.RegisterServices()
if err := s.InitAdmin(ctx); err != nil {
return err
}
s.RegisterServices(ctx, true)
l, err := s.setupLeaderElection()
if err != nil {
@@ -149,7 +140,7 @@ func (s *restServer) setupLeaderElection() (*leaderelection.LeaderElectionConfig
RetryPeriod: time.Second * 2,
Callbacks: leaderelection.LeaderCallbacks{
OnStartedLeading: func(ctx context.Context) {
go velasync.Start(ctx, s.dataStore, restCfg)
go velasync.Start(ctx, s.dataStore, restCfg, s.usecases)
s.runWorkflowRecordSync(ctx, s.cfg.LeaderConfig.Duration)
},
OnStoppedLeading: func() {
@@ -169,10 +160,9 @@ func (s *restServer) setupLeaderElection() (*leaderelection.LeaderElectionConfig
}, nil
}
func (s restServer) runWorkflowRecordSync(ctx context.Context, duration time.Duration) {
func (s *restServer) runWorkflowRecordSync(ctx context.Context, duration time.Duration) {
klog.Infof("start to syncing workflow record")
w := usecase.NewWorkflowUsecase(s.dataStore, usecase.NewEnvUsecase(s.dataStore))
w := s.usecases["workflow"].(usecase.WorkflowUsecase)
t := time.NewTicker(duration)
defer t.Stop()
@@ -189,8 +179,9 @@ func (s restServer) runWorkflowRecordSync(ctx context.Context, duration time.Dur
}
// RegisterServices register web service
func (s *restServer) RegisterServices() restfulspec.Config {
webservice.Init(s.dataStore, s.cfg.AddonCacheTime)
func (s *restServer) RegisterServices(ctx context.Context, initDatabase bool) restfulspec.Config {
s.usecases = webservice.Init(ctx, s.dataStore, s.cfg.AddonCacheTime, initDatabase)
/* ************************************************************** */
/* ************* Open API Route Group ***************** */
/* ************************************************************** */
@@ -223,57 +214,6 @@ func (s *restServer) RegisterServices() restfulspec.Config {
return config
}
// RegisterServices register web service
func (s *restServer) InitAdmin(ctx context.Context) error {
admin := "admin"
if err := s.dataStore.Get(ctx, &model.User{
Name: admin,
}); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
pwd := utils2.RandomString(8)
encrypted, err := usecase.GeneratePasswordHash(pwd)
if err != nil {
return err
}
if err := s.dataStore.Add(ctx, &model.User{
Name: admin,
Password: encrypted,
}); err != nil {
return err
}
kubecli, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("failed to get kube client: %s", err.Error())
return err
}
secret := &corev1.Secret{}
if err := kubecli.Get(ctx, k8stypes.NamespacedName{
Name: admin,
Namespace: types.DefaultKubeVelaNS,
}, secret); err != nil {
if apierrors.IsNotFound(err) {
if err := kubecli.Create(ctx, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: admin,
Namespace: types.DefaultKubeVelaNS,
},
StringData: map[string]string{
admin: pwd,
},
}); err != nil {
return err
}
} else {
return err
}
}
} else {
return err
}
}
return nil
}
func (s *restServer) requestLog(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
start := time.Now()
c := utils.NewResponseCapture(resp.ResponseWriter)
+66 -26
View File
@@ -48,7 +48,7 @@ import (
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
"github.com/oam-dev/kubevela/pkg/apiserver/sync"
syncconvert "github.com/oam-dev/kubevela/pkg/apiserver/sync/convert"
"github.com/oam-dev/kubevela/pkg/appfile/dryrun"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
@@ -130,7 +130,7 @@ func NewApplicationUsecase(ds datastore.DataStore,
) ApplicationUsecase {
kubecli, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("get kubeclient failure %s", err.Error())
log.Logger.Fatalf("get kube client failure %s", err.Error())
}
return &applicationUsecaseImpl{
ds: ds,
@@ -147,9 +147,6 @@ func NewApplicationUsecase(ds datastore.DataStore,
func listApp(ctx context.Context, ds datastore.DataStore, listOptions apisv1.ListApplicationOptions) ([]*model.Application, error) {
var app = model.Application{}
if listOptions.Project != "" {
app.Project = listOptions.Project
}
var err error
var envBinding []*apisv1.EnvBindingBase
if listOptions.Env != "" || listOptions.TargetName != "" {
@@ -159,8 +156,14 @@ func listApp(ctx context.Context, ds datastore.DataStore, listOptions apisv1.Lis
return nil, err
}
}
entities, err := ds.List(ctx, &app, &datastore.ListOptions{})
var filterOptions datastore.FilterOptions
if len(listOptions.Projects) > 0 {
filterOptions.In = append(filterOptions.In, datastore.InQueryOption{
Key: "project",
Values: listOptions.Projects,
})
}
entities, err := ds.List(ctx, &app, &datastore.ListOptions{FilterOptions: filterOptions})
if err != nil {
return nil, err
}
@@ -202,13 +205,36 @@ func listApp(ctx context.Context, ds datastore.DataStore, listOptions apisv1.Lis
// ListApplications list applications
func (c *applicationUsecaseImpl) ListApplications(ctx context.Context, listOptions apisv1.ListApplicationOptions) ([]*apisv1.ApplicationBase, error) {
userName, ok := ctx.Value(&apisv1.CtxKeyUser).(string)
if !ok {
return nil, bcode.ErrUnauthorized
}
projects, err := c.projectUsecase.ListUserProjects(ctx, userName)
if err != nil {
return nil, err
}
var availableProjectNames []string
for _, project := range projects {
availableProjectNames = append(availableProjectNames, project.Name)
}
if len(availableProjectNames) == 0 {
return []*apisv1.ApplicationBase{}, nil
}
if len(listOptions.Projects) > 0 {
if !utils2.SliceIncludeSlice(availableProjectNames, listOptions.Projects) {
return []*apisv1.ApplicationBase{}, nil
}
}
if len(listOptions.Projects) == 0 {
listOptions.Projects = availableProjectNames
}
apps, err := listApp(ctx, c.ds, listOptions)
if err != nil {
return nil, err
}
var list []*apisv1.ApplicationBase
for _, app := range apps {
appBase := c.convertAppModelToBase(ctx, app)
appBase := c.convertAppModelToBase(app, projects)
list = append(list, appBase)
}
sort.Slice(list, func(i, j int) bool {
@@ -233,8 +259,16 @@ func (c *applicationUsecaseImpl) GetApplication(ctx context.Context, appName str
// DetailApplication detail application info
func (c *applicationUsecaseImpl) DetailApplication(ctx context.Context, app *model.Application) (*apisv1.DetailApplicationResponse, error) {
base := c.convertAppModelToBase(ctx, app)
policys, err := c.queryApplicationPolicies(ctx, app)
var project *apisv1.ProjectBase
if app.Project != "" {
var err error
project, err = c.projectUsecase.DetailProject(ctx, app.Project)
if err != nil {
return nil, bcode.ErrProjectIsNotExist
}
}
base := c.convertAppModelToBase(app, []*apisv1.ProjectBase{project})
policies, err := c.queryApplicationPolicies(ctx, app)
if err != nil {
return nil, err
}
@@ -248,7 +282,7 @@ func (c *applicationUsecaseImpl) DetailApplication(ctx context.Context, app *mod
}
var policyNames []string
var envBindingNames []string
for _, p := range policys {
for _, p := range policies {
policyNames = append(policyNames, p.Name)
}
for _, e := range envBindings {
@@ -349,9 +383,9 @@ func (c *applicationUsecaseImpl) CreateApplication(ctx context.Context, req apis
return nil, bcode.ErrApplicationExist
}
// check project
project, err := c.projectUsecase.GetProject(ctx, req.Project)
project, err := c.projectUsecase.DetailProject(ctx, req.Project)
if err != nil {
return nil, err
return nil, bcode.ErrProjectIsNotExist
}
application.Project = project.Name
@@ -385,7 +419,7 @@ func (c *applicationUsecaseImpl) CreateApplication(ctx context.Context, req apis
return nil, err
}
// render app base info.
base := c.convertAppModelToBase(ctx, &application)
base := c.convertAppModelToBase(&application, []*apisv1.ProjectBase{project})
return base, nil
}
@@ -506,6 +540,14 @@ func (c *applicationUsecaseImpl) saveApplicationEnvBinding(ctx context.Context,
}
func (c *applicationUsecaseImpl) UpdateApplication(ctx context.Context, app *model.Application, req apisv1.UpdateApplicationRequest) (*apisv1.ApplicationBase, error) {
var project *apisv1.ProjectBase
if app.Project != "" {
var err error
project, err = c.projectUsecase.DetailProject(ctx, app.Project)
if err != nil {
return nil, bcode.ErrProjectIsNotExist
}
}
app.Alias = req.Alias
app.Description = req.Description
app.Labels = req.Labels
@@ -513,7 +555,7 @@ func (c *applicationUsecaseImpl) UpdateApplication(ctx context.Context, app *mod
if err := c.ds.Put(ctx, app); err != nil {
return nil, err
}
return c.convertAppModelToBase(ctx, app), nil
return c.convertAppModelToBase(app, []*apisv1.ProjectBase{project}), nil
}
// ListRecords list application record
@@ -617,11 +659,11 @@ func (c *applicationUsecaseImpl) queryApplicationPolicies(ctx context.Context, a
var policy = model.ApplicationPolicy{
AppPrimaryKey: app.PrimaryKey(),
}
policys, err := c.ds.List(ctx, &policy, &datastore.ListOptions{})
policies, err := c.ds.List(ctx, &policy, &datastore.ListOptions{})
if err != nil {
return nil, err
}
for _, policy := range policys {
for _, policy := range policies {
pm := policy.(*model.ApplicationPolicy)
list = append(list, pm)
}
@@ -893,7 +935,7 @@ func (c *applicationUsecaseImpl) renderOAMApplication(ctx context.Context, appMo
return app, nil
}
func (c *applicationUsecaseImpl) convertAppModelToBase(ctx context.Context, app *model.Application) *apisv1.ApplicationBase {
func (c *applicationUsecaseImpl) convertAppModelToBase(app *model.Application, projects []*apisv1.ProjectBase) *apisv1.ApplicationBase {
appBase := &apisv1.ApplicationBase{
Name: app.Name,
Alias: app.Alias,
@@ -902,17 +944,15 @@ func (c *applicationUsecaseImpl) convertAppModelToBase(ctx context.Context, app
Description: app.Description,
Icon: app.Icon,
Labels: app.Labels,
Project: &apisv1.ProjectBase{Name: app.Project},
}
if app.IsSynced() {
appBase.ReadOnly = true
}
project, err := c.projectUsecase.GetProject(ctx, app.Project)
if err != nil {
log.Logger.Errorf("query project info failure %s", err.Error())
}
if project != nil {
appBase.Project = convertProjectModel2Base(project)
for _, project := range projects {
if project.Name == app.Project {
appBase.Project = project
}
}
return appBase
}
@@ -1610,7 +1650,7 @@ func (c *applicationUsecaseImpl) resetApp(ctx context.Context, targetApp *v1beta
for _, comp := range targetComps {
// add or update new app's components from old app
if utils.StringsContain(readyToAdd, comp.Name) || utils.StringsContain(readyToUpdate, comp.Name) {
compModel, err := sync.ConvertFromCRComponent(appPrimaryKey, comp)
compModel, err := syncconvert.FromCRComponent(appPrimaryKey, comp)
if err != nil {
return &apisv1.AppResetResponse{}, bcode.ErrInvalidProperties
}
+26 -11
View File
@@ -42,11 +42,13 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/pkg/utils/apply"
)
var _ = Describe("Test application usecase function", func() {
var (
rbacUsecase *rbacUsecaseImpl
appUsecase *applicationUsecaseImpl
workflowUsecase *workflowUsecaseImpl
envUsecase *envUsecaseImpl
@@ -54,6 +56,7 @@ var _ = Describe("Test application usecase function", func() {
targetUsecase *targetUsecaseImpl
definitionUsecase *definitionUsecaseImpl
projectUsecase *projectUsecaseImpl
userUsecase *userUsecaseImpl
testProject = "app-project"
testApp = "test-app"
defaultTarget = "default"
@@ -66,12 +69,14 @@ var _ = Describe("Test application usecase function", func() {
ds, err := NewDatastore(datastore.Config{Type: "kubeapi", Database: "app-test-kubevela"})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
envUsecase = &envUsecaseImpl{ds: ds, kubeClient: k8sClient}
rbacUsecase = &rbacUsecaseImpl{ds: ds}
userUsecase = &userUsecaseImpl{ds: ds, k8sClient: k8sClient}
projectUsecase = &projectUsecaseImpl{ds: ds, k8sClient: k8sClient, rbacUsecase: rbacUsecase}
envUsecase = &envUsecaseImpl{ds: ds, kubeClient: k8sClient, projectUsecase: projectUsecase}
workflowUsecase = &workflowUsecaseImpl{ds: ds, envUsecase: envUsecase}
definitionUsecase = &definitionUsecaseImpl{kubeClient: k8sClient, caches: utils.NewMemoryCacheStore(context.Background())}
envBindingUsecase = &envBindingUsecaseImpl{ds: ds, envUsecase: envUsecase, workflowUsecase: workflowUsecase, kubeClient: k8sClient, definitionUsecase: definitionUsecase}
targetUsecase = &targetUsecaseImpl{ds: ds, k8sClient: k8sClient}
projectUsecase = &projectUsecaseImpl{ds: ds, k8sClient: k8sClient}
appUsecase = &applicationUsecaseImpl{
ds: ds,
workflowUsecase: workflowUsecase,
@@ -87,11 +92,19 @@ var _ = Describe("Test application usecase function", func() {
It("Test CreateApplication function", func() {
By("test sample create")
_, err := targetUsecase.CreateTarget(context.TODO(), v1.CreateTargetRequest{Name: defaultTarget, Cluster: &v1.ClusterTarget{ClusterName: "local", Namespace: namespace1}})
By("init default admin user")
var ns = corev1.Namespace{}
ns.Name = types.DefaultKubeVelaNS
err := k8sClient.Create(context.TODO(), &ns)
Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
err = userUsecase.Init(context.TODO())
Expect(err).Should(BeNil())
_, err = projectUsecase.CreateProject(context.TODO(), v1.CreateProjectRequest{Name: testProject})
By("prepare test project")
_, err = projectUsecase.CreateProject(context.TODO(), v1.CreateProjectRequest{Name: testProject, Owner: model.DefaultAdminUserName})
Expect(err).Should(BeNil())
_, err = targetUsecase.CreateTarget(context.TODO(), v1.CreateTargetRequest{Name: defaultTarget, Project: testProject, Cluster: &v1.ClusterTarget{ClusterName: "local", Namespace: namespace1}})
Expect(err).Should(BeNil())
_, err = envUsecase.CreateEnv(context.TODO(), v1.CreateEnvRequest{Name: "app-dev", Namespace: envnsdev, Targets: []string{defaultTarget}, Project: "app-dev"})
@@ -114,6 +127,7 @@ var _ = Describe("Test application usecase function", func() {
Properties: "{\"image\":\"nginx\"}",
},
}
By("test create application")
base, err := appUsecase.CreateApplication(context.TODO(), req)
Expect(err).Should(BeNil())
Expect(cmp.Diff(base.Description, req.Description)).Should(BeEmpty())
@@ -124,13 +138,13 @@ var _ = Describe("Test application usecase function", func() {
})
It("Test ListApplications function", func() {
_, err := appUsecase.ListApplications(context.TODO(), v1.ListApplicationOptions{})
_, err := appUsecase.ListApplications(context.WithValue(context.TODO(), &v1.CtxKeyUser, model.DefaultAdminUserName), v1.ListApplicationOptions{})
Expect(err).Should(BeNil())
})
It("Test ListApplications and filter by targetName function", func() {
list, err := appUsecase.ListApplications(context.TODO(), v1.ListApplicationOptions{
Project: testProject,
list, err := appUsecase.ListApplications(context.WithValue(context.TODO(), &v1.CtxKeyUser, model.DefaultAdminUserName), v1.ListApplicationOptions{
Projects: []string{testProject},
TargetName: defaultTarget})
Expect(err).Should(BeNil())
Expect(cmp.Diff(len(list), 1)).Should(BeEmpty())
@@ -481,7 +495,7 @@ var _ = Describe("Test application usecase function", func() {
Expect(cmp.Diff(compareResponse.IsDiff, false)).Should(BeEmpty())
By("compare when app's env add target, should return true")
_, err = targetUsecase.CreateTarget(context.TODO(), v1.CreateTargetRequest{Name: "dev-target1", Cluster: &v1.ClusterTarget{ClusterName: "local", Namespace: "dev-target1"}})
_, err = targetUsecase.CreateTarget(context.TODO(), v1.CreateTargetRequest{Name: "dev-target1", Project: appModel.Project, Cluster: &v1.ClusterTarget{ClusterName: "local", Namespace: "dev-target1"}})
Expect(err).Should(BeNil())
_, err = envUsecase.UpdateEnv(context.TODO(), "app-dev",
v1.UpdateEnvRequest{
@@ -594,8 +608,9 @@ var _ = Describe("Test application component usecase function", func() {
ds, err := NewDatastore(datastore.Config{Type: "kubeapi", Database: "app-test-kubevela"})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
projectUsecase = &projectUsecaseImpl{ds: ds, k8sClient: k8sClient}
envUsecase = &envUsecaseImpl{ds: ds, kubeClient: k8sClient}
rbacUsecase := &rbacUsecaseImpl{ds: ds}
projectUsecase = &projectUsecaseImpl{ds: ds, k8sClient: k8sClient, rbacUsecase: rbacUsecase}
envUsecase = &envUsecaseImpl{ds: ds, kubeClient: k8sClient, projectUsecase: projectUsecase}
workflowUsecase := &workflowUsecaseImpl{ds: ds, envUsecase: envUsecase}
envBindingUsecase := &envBindingUsecaseImpl{ds: ds, envUsecase: envUsecase, workflowUsecase: workflowUsecase, kubeClient: k8sClient}
+13 -2
View File
@@ -27,6 +27,7 @@ import (
"github.com/form3tech-oss/jwt-go"
"golang.org/x/oauth2"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
@@ -172,6 +173,9 @@ func (a *authenticationUsecaseImpl) Login(ctx context.Context, loginReq apisv1.L
if err != nil {
return nil, err
}
if userBase.Disabled {
return nil, bcode.ErrUserAlreadyDisabled
}
accessToken, err := a.generateJWTToken(userBase.Name, GrantTypeAccess, time.Hour)
if err != nil {
return nil, err
@@ -255,6 +259,9 @@ func (a *authenticationUsecaseImpl) GetDexConfig(ctx context.Context) (*apisv1.D
Name: secretDexConfig,
Namespace: velatypes.DefaultKubeVelaNS,
}, secret); err != nil {
if apierrors.IsNotFound(err) {
return nil, bcode.ErrDexConfigNotFound
}
log.Logger.Errorf("failed to get dex config: %s", err.Error())
return nil, err
}
@@ -287,8 +294,12 @@ func (a *authenticationUsecaseImpl) GetLoginType(ctx context.Context) (*apisv1.G
if err != nil {
return nil, err
}
loginType := sysInfo.LoginType
if loginType == "" {
loginType = model.LoginTypeLocal
}
return &apisv1.GetLoginTypeResponse{
LoginType: sysInfo.LoginType,
LoginType: loginType,
}, nil
}
@@ -337,7 +348,7 @@ func (l *localHandlerImpl) login(ctx context.Context) (*apisv1.UserBase, error)
if err := compareHashWithPassword(user.Password, l.password); err != nil {
return nil, err
}
if err := l.userUsecase.updateUserLoginTime(ctx, user); err != nil {
if err := l.userUsecase.UpdateUserLoginTime(ctx, user); err != nil {
return nil, err
}
return &apisv1.UserBase{
@@ -179,11 +179,6 @@ var _ = Describe("Test namespace usecase functions", func() {
Expect(cmp.Diff(len(uiSchema), 12)).Should(BeEmpty())
Expect(cmp.Diff(uiSchema[7].JSONKey, "livenessProbe")).Should(BeEmpty())
Expect(cmp.Diff(len(uiSchema[7].SubParameters), 8)).Should(BeEmpty())
outdata, err := yaml.Marshal(uiSchema)
Expect(err).Should(Succeed())
err = ioutil.WriteFile("./testdata/ui-schema.yaml", outdata, 0755)
Expect(err).Should(Succeed())
})
It("Test sortDefaultUISchema", testSortDefaultUISchema)
+58 -21
View File
@@ -39,23 +39,25 @@ import (
type EnvUsecase interface {
GetEnv(ctx context.Context, envName string) (*model.Env, error)
ListEnvs(ctx context.Context, page, pageSize int, listOption apisv1.ListEnvOptions) (*apisv1.ListEnvResponse, error)
ListEnvCount(ctx context.Context, listOption apisv1.ListEnvOptions) (int64, error)
DeleteEnv(ctx context.Context, envName string) error
CreateEnv(ctx context.Context, req apisv1.CreateEnvRequest) (*apisv1.Env, error)
UpdateEnv(ctx context.Context, envName string, req apisv1.UpdateEnvRequest) (*apisv1.Env, error)
}
type envUsecaseImpl struct {
ds datastore.DataStore
kubeClient client.Client
ds datastore.DataStore
projectUsecase ProjectUsecase
kubeClient client.Client
}
// NewEnvUsecase new env usecase
func NewEnvUsecase(ds datastore.DataStore) EnvUsecase {
func NewEnvUsecase(ds datastore.DataStore, projectUsecase ProjectUsecase) EnvUsecase {
kubecli, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("get kubeclient failure %s", err.Error())
}
return &envUsecaseImpl{kubeClient: kubecli, ds: ds}
return &envUsecaseImpl{kubeClient: kubecli, ds: ds, projectUsecase: projectUsecase}
}
// GetEnv get env
@@ -96,40 +98,75 @@ func (p *envUsecaseImpl) DeleteEnv(ctx context.Context, envName string) error {
// ListEnvs list envs
func (p *envUsecaseImpl) ListEnvs(ctx context.Context, page, pageSize int, listOption apisv1.ListEnvOptions) (*apisv1.ListEnvResponse, error) {
entities, err := listEnvs(ctx, p.ds, listOption.Project, &datastore.ListOptions{Page: page, PageSize: pageSize, SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}})
userName, ok := ctx.Value(&apisv1.CtxKeyUser).(string)
if !ok {
return nil, bcode.ErrUnauthorized
}
projects, err := p.projectUsecase.ListUserProjects(ctx, userName)
if err != nil {
return nil, err
}
var availableProjectNames []string
var projectNameAlias = make(map[string]string)
for _, project := range projects {
availableProjectNames = append(availableProjectNames, project.Name)
projectNameAlias[project.Name] = project.Alias
}
if len(availableProjectNames) == 0 {
return &apisv1.ListEnvResponse{Envs: []*apisv1.Env{}, Total: 0}, nil
}
if listOption.Project != "" {
if !util.StringsContain(availableProjectNames, listOption.Project) {
return &apisv1.ListEnvResponse{Envs: []*apisv1.Env{}, Total: 0}, nil
}
}
projectNames := []string{listOption.Project}
if listOption.Project == "" {
projectNames = availableProjectNames
}
filter := datastore.FilterOptions{
In: []datastore.InQueryOption{
{
Key: "project",
Values: projectNames,
},
},
}
entities, err := listEnvs(ctx, p.ds, &datastore.ListOptions{
Page: page,
PageSize: pageSize,
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}},
FilterOptions: filter,
})
if err != nil {
return nil, err
}
Targets, err := listTarget(ctx, p.ds, nil)
targets, err := listTarget(ctx, p.ds, listOption.Project, nil)
if err != nil {
return nil, err
}
var envs []*apisv1.Env
for _, ee := range entities {
envs = append(envs, convertEnvModel2Base(ee, Targets))
envs = append(envs, convertEnvModel2Base(ee, targets))
}
projectResp, err := listProjects(ctx, p.ds, 0, 0)
if err != nil {
return nil, err
for i := range envs {
envs[i].Project.Alias = projectNameAlias[envs[i].Project.Name]
}
for _, e := range envs {
for _, pj := range projectResp.Projects {
if e.Project.Name == pj.Name {
e.Project.Alias = pj.Alias
break
}
}
}
total, err := p.ds.Count(ctx, &model.Env{Project: listOption.Project}, nil)
total, err := p.ds.Count(ctx, &model.Env{Project: listOption.Project}, &filter)
if err != nil {
return nil, err
}
return &apisv1.ListEnvResponse{Envs: envs, Total: total}, nil
}
func (p *envUsecaseImpl) ListEnvCount(ctx context.Context, listOption apisv1.ListEnvOptions) (int64, error) {
return p.ds.Count(ctx, &model.Env{Project: listOption.Project}, nil)
}
func checkEqual(old, new []string) bool {
if old == nil && new == nil {
return true
@@ -186,7 +223,7 @@ func (p *envUsecaseImpl) UpdateEnv(ctx context.Context, name string, req apisv1.
env.Targets = req.Targets
}
targets, err := listTarget(ctx, p.ds, nil)
targets, err := listTarget(ctx, p.ds, "", nil)
if err != nil {
return nil, err
}
@@ -232,7 +269,7 @@ func (p *envUsecaseImpl) CreateEnv(ctx context.Context, req apisv1.CreateEnvRequ
return nil, bcode.ErrEnvTargetConflict
}
targets, err := listTarget(ctx, p.ds, nil)
targets, err := listTarget(ctx, p.ds, "", nil)
if err != nil {
return nil, err
}
+1 -4
View File
@@ -79,11 +79,8 @@ func getEnv(ctx context.Context, ds datastore.DataStore, envName string) (*model
return env, nil
}
func listEnvs(ctx context.Context, ds datastore.DataStore, project string, listOption *datastore.ListOptions) ([]*model.Env, error) {
func listEnvs(ctx context.Context, ds datastore.DataStore, listOption *datastore.ListOptions) ([]*model.Env, error) {
var env = model.Env{}
if project != "" {
env.Project = project
}
entities, err := ds.List(ctx, &env, listOption)
if err != nil {
return nil, err
+4 -2
View File
@@ -43,7 +43,9 @@ var _ = Describe("Test env usecase functions", func() {
ds, err = NewDatastore(datastore.Config{Type: "kubeapi", Database: "env-test-kubevela"})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
envUsecase = &envUsecaseImpl{kubeClient: k8sClient, ds: ds}
rbacUsecase := &rbacUsecaseImpl{ds: ds}
projectUsecase := &projectUsecaseImpl{ds: ds, k8sClient: k8sClient, rbacUsecase: rbacUsecase}
envUsecase = &envUsecaseImpl{kubeClient: k8sClient, ds: ds, projectUsecase: projectUsecase}
})
It("Test Create/Get/Delete Env function", func() {
// create target
@@ -111,7 +113,7 @@ var _ = Describe("Test env usecase functions", func() {
Expect(err).Should(BeNil())
By("Test ListEnvs function")
_, err = envUsecase.ListEnvs(context.TODO(), 1, 1, apisv1.ListEnvOptions{})
_, err = envUsecase.ListEnvs(context.WithValue(context.TODO(), &apisv1.CtxKeyUser, "admin"), 1, 1, apisv1.ListEnvOptions{})
Expect(err).Should(BeNil())
})
+17 -6
View File
@@ -95,15 +95,26 @@ func pickEnv(envs []*model.Env, name string) (*model.Env, error) {
func listFullEnvBinding(ctx context.Context, ds datastore.DataStore, option envListOption) ([]*apisv1.EnvBindingBase, error) {
envBindings, err := listEnvBindings(ctx, ds, option)
if err != nil {
return nil, bcode.ErrEnvBindingsNotExist
}
targets, err := listTarget(ctx, ds, nil)
targets, err := listTarget(ctx, ds, "", nil)
if err != nil {
return nil, err
}
// TODO: list by project
envs, err := listEnvs(ctx, ds, "", nil)
var listOption *datastore.ListOptions
if option.projectName != "" {
listOption = &datastore.ListOptions{
FilterOptions: datastore.FilterOptions{
In: []datastore.InQueryOption{
{
Key: "project",
Values: []string{option.projectName},
},
},
},
}
}
envs, err := listEnvs(ctx, ds, listOption)
if err != nil {
return nil, err
}
@@ -120,7 +131,7 @@ func listFullEnvBinding(ctx context.Context, ds datastore.DataStore, option envL
}
func (e *envBindingUsecaseImpl) GetEnvBindings(ctx context.Context, app *model.Application) ([]*apisv1.EnvBindingBase, error) {
full, err := listFullEnvBinding(ctx, e.ds, envListOption{appPrimaryKey: app.PrimaryKey()})
full, err := listFullEnvBinding(ctx, e.ds, envListOption{appPrimaryKey: app.PrimaryKey(), projectName: app.Project})
if err != nil {
log.Logger.Errorf("list envbinding for app %s err: %v\n", app.Name, err)
return nil, err
@@ -306,7 +317,7 @@ func (e *envBindingUsecaseImpl) deleteEnvWorkflow(ctx context.Context, app *mode
}
func (e *envBindingUsecaseImpl) DetailEnvBinding(ctx context.Context, app *model.Application, envBinding *model.EnvBinding) (*apisv1.DetailEnvBindingResponse, error) {
targets, err := listTarget(ctx, e.ds, nil)
targets, err := listTarget(ctx, e.ds, "", nil)
if err != nil {
return nil, err
}
@@ -26,6 +26,7 @@ import (
type envListOption struct {
appPrimaryKey string
envName string
projectName string
}
func listEnvBindings(ctx context.Context, ds datastore.DataStore, listOption envListOption) ([]*model.EnvBinding, error) {
@@ -48,7 +48,9 @@ var _ = Describe("Test envBindingUsecase functions", func() {
testApp = &model.Application{
Name: "test-app-env",
}
envUsecase = &envUsecaseImpl{ds: ds, kubeClient: k8sClient}
rbacUsecase := &rbacUsecaseImpl{ds: ds}
projectUsecase := &projectUsecaseImpl{ds: ds, k8sClient: k8sClient, rbacUsecase: rbacUsecase}
envUsecase = &envUsecaseImpl{ds: ds, kubeClient: k8sClient, projectUsecase: projectUsecase}
workflowUsecase = &workflowUsecaseImpl{ds: ds, kubeClient: k8sClient, envUsecase: envUsecase}
definitionUsecase = &definitionUsecaseImpl{kubeClient: k8sClient, caches: utils.NewMemoryCacheStore(context.TODO())}
envBindingUsecase = &envBindingUsecaseImpl{ds: ds, workflowUsecase: workflowUsecase, definitionUsecase: definitionUsecase, kubeClient: k8sClient, envUsecase: envUsecase}
+329 -37
View File
@@ -19,6 +19,7 @@ package usecase
import (
"context"
"errors"
"fmt"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -34,44 +35,80 @@ import (
// ProjectUsecase project manage usecase.
type ProjectUsecase interface {
GetProject(ctx context.Context, projectName string) (*model.Project, error)
DetailProject(ctx context.Context, projectName string) (*apisv1.ProjectBase, error)
ListProjects(ctx context.Context, page, pageSize int) (*apisv1.ListProjectResponse, error)
ListUserProjects(ctx context.Context, userName string) ([]*apisv1.ProjectBase, error)
CreateProject(ctx context.Context, req apisv1.CreateProjectRequest) (*apisv1.ProjectBase, error)
DeleteProject(ctx context.Context, projectName string) error
UpdateProject(ctx context.Context, projectName string, req apisv1.UpdateProjectRequest) (*apisv1.ProjectBase, error)
ListProjectUser(ctx context.Context, projectName string, page, pageSize int) (*apisv1.ListProjectUsersResponse, error)
AddProjectUser(ctx context.Context, projectName string, req apisv1.AddProjectUserRequest) (*apisv1.ProjectUserBase, error)
DeleteProjectUser(ctx context.Context, projectName string, userName string) error
UpdateProjectUser(ctx context.Context, projectName string, userName string, req apisv1.UpdateProjectUserRequest) (*apisv1.ProjectUserBase, error)
Init(ctx context.Context) error
}
type projectUsecaseImpl struct {
ds datastore.DataStore
k8sClient client.Client
ds datastore.DataStore
k8sClient client.Client
rbacUsecase RBACUsecase
}
// NewProjectUsecase new project usecase
func NewProjectUsecase(ds datastore.DataStore) ProjectUsecase {
func NewProjectUsecase(ds datastore.DataStore, rbacUsecase RBACUsecase) ProjectUsecase {
k8sClient, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("get k8sClient failure: %s", err.Error())
}
p := &projectUsecaseImpl{ds: ds, k8sClient: k8sClient}
p.initDefaultProjectEnvTarget(model.DefaultInitNamespace)
p := &projectUsecaseImpl{ds: ds, k8sClient: k8sClient, rbacUsecase: rbacUsecase}
return p
}
// Init init default data
func (p *projectUsecaseImpl) Init(ctx context.Context) error {
return p.InitDefaultProjectEnvTarget(ctx, model.DefaultInitNamespace)
}
// initDefaultProjectEnvTarget will initialize a default project with a default env that contain a default target
// the default env and default target both using the `default` namespace in control plane cluster
func (p *projectUsecaseImpl) initDefaultProjectEnvTarget(defaultNamespace string) {
ctx := context.Background()
projResp, err := listProjects(ctx, p.ds, 0, 0)
func (p *projectUsecaseImpl) InitDefaultProjectEnvTarget(ctx context.Context, defaultNamespace string) error {
var project = model.Project{}
entities, err := p.ds.List(ctx, &project, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{
IsNotExist: []datastore.IsNotExistQueryOption{
{
Key: "owner",
},
},
}})
if err != nil {
log.Logger.Errorf("initialize project failed %v", err)
return
return fmt.Errorf("initialize project failed %w", err)
}
if len(projResp.Projects) > 0 {
return
if len(entities) > 0 {
for _, project := range entities {
pro := project.(*model.Project)
var init = pro.Owner == ""
pro.Owner = model.DefaultAdminUserName
if err := p.ds.Put(ctx, pro); err != nil {
return err
}
// owner is empty, it is old data
if init {
if err := p.rbacUsecase.InitDefaultRoleAndUsersForProject(ctx, pro); err != nil {
return fmt.Errorf("init default role and users for project %s failure %w", pro.Name, err)
}
}
}
return nil
}
count, _ := p.ds.Count(ctx, &project, nil)
if count > 0 {
return nil
}
log.Logger.Info("no default project found, adding a default project with default env and target")
if err := createTargetNamespace(ctx, p.k8sClient, multicluster.ClusterLocalName, defaultNamespace, model.DefaultInitName); err != nil {
log.Logger.Errorf("initialize default target namespace failed %v", err)
return
return fmt.Errorf("initialize default target namespace failed %w", err)
}
// initialize default target first
err = createTarget(ctx, p.ds, &model.Target{
@@ -85,8 +122,7 @@ func (p *projectUsecaseImpl) initDefaultProjectEnvTarget(defaultNamespace string
})
// for idempotence, ignore default target already exist error
if err != nil && errors.Is(err, bcode.ErrTargetExist) {
log.Logger.Errorf("initialize default target failed %v", err)
return
return fmt.Errorf("initialize default target failed %w", err)
}
// initialize default target first
@@ -100,20 +136,20 @@ func (p *projectUsecaseImpl) initDefaultProjectEnvTarget(defaultNamespace string
})
// for idempotence, ignore default env already exist error
if err != nil && errors.Is(err, bcode.ErrEnvAlreadyExists) {
log.Logger.Errorf("initialize default environment failed %v", err)
return
return fmt.Errorf("initialize default environment failed %w", err)
}
_, err = p.CreateProject(ctx, apisv1.CreateProjectRequest{
Name: model.DefaultInitName,
Alias: "Default",
Description: model.DefaultProjectDescription,
Owner: model.DefaultAdminUserName,
})
if err != nil {
log.Logger.Errorf("initialize project failed %v", err)
return
return fmt.Errorf("initialize project failed %w", err)
}
return nil
}
// GetProject get project
@@ -128,24 +164,73 @@ func (p *projectUsecaseImpl) GetProject(ctx context.Context, projectName string)
return project, nil
}
func (p *projectUsecaseImpl) DetailProject(ctx context.Context, projectName string) (*apisv1.ProjectBase, error) {
project, err := p.GetProject(ctx, projectName)
if err != nil {
return nil, err
}
var user = &model.User{Name: project.Owner}
if project.Owner != "" {
if err := p.ds.Get(ctx, user); err != nil {
log.Logger.Warnf("get project owner %s info failure %s", project.Owner, err.Error())
}
}
return ConvertProjectModel2Base(project, user), nil
}
func listProjects(ctx context.Context, ds datastore.DataStore, page, pageSize int) (*apisv1.ListProjectResponse, error) {
var project = model.Project{}
entitys, err := ds.List(ctx, &project, &datastore.ListOptions{Page: page, PageSize: pageSize, SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}})
entities, err := ds.List(ctx, &project, &datastore.ListOptions{Page: page, PageSize: pageSize, SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}})
if err != nil {
return nil, err
}
var projects []*apisv1.ProjectBase
for _, entity := range entitys {
for _, entity := range entities {
project := entity.(*model.Project)
projects = append(projects, convertProjectModel2Base(project))
var user = &model.User{Name: project.Owner}
if project.Owner != "" {
if err := ds.Get(ctx, user); err != nil {
log.Logger.Warnf("get project owner %s info failure %s", project.Owner, err.Error())
}
}
projects = append(projects, ConvertProjectModel2Base(project, user))
}
total, err := ds.Count(ctx, &model.Project{}, nil)
total, err := ds.Count(ctx, &project, nil)
if err != nil {
return nil, err
}
return &apisv1.ListProjectResponse{Projects: projects, Total: total}, nil
}
func (p *projectUsecaseImpl) ListUserProjects(ctx context.Context, userName string) ([]*apisv1.ProjectBase, error) {
var projectUser = model.ProjectUser{
Username: userName,
}
entities, err := p.ds.List(ctx, &projectUser, nil)
if err != nil {
return nil, err
}
var projectNames []string
for _, entity := range entities {
projectNames = append(projectNames, entity.(*model.ProjectUser).ProjectName)
}
if len(projectNames) == 0 {
return []*apisv1.ProjectBase{}, nil
}
projectEntities, err := p.ds.List(ctx, &model.Project{}, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{In: []datastore.InQueryOption{{
Key: "name",
Values: projectNames,
}}}})
if err != nil {
return nil, err
}
var projectBases []*apisv1.ProjectBase
for _, entity := range projectEntities {
projectBases = append(projectBases, ConvertProjectModel2Base(entity.(*model.Project), nil))
}
return projectBases, nil
}
// ListProjects list projects
func (p *projectUsecaseImpl) ListProjects(ctx context.Context, page, pageSize int) (*apisv1.ListProjectResponse, error) {
return listProjects(ctx, p.ds, page, pageSize)
@@ -153,10 +238,58 @@ func (p *projectUsecaseImpl) ListProjects(ctx context.Context, page, pageSize in
// DeleteProject delete a project
func (p *projectUsecaseImpl) DeleteProject(ctx context.Context, name string) error {
_, err := p.GetProject(ctx, name)
if err != nil {
return err
}
// TODO(@wonderflow): it's not supported for delete a project now, just used in test
// we should prevent delete a project that contain any application/env inside.
count, err := p.ds.Count(ctx, &model.Application{Project: name}, nil)
if err != nil {
return err
}
if count > 0 {
return bcode.ErrProjectDenyDeleteByApplication
}
count, err = p.ds.Count(ctx, &model.Target{Project: name}, nil)
if err != nil {
return err
}
if count > 0 {
return bcode.ErrProjectDenyDeleteByTarget
}
count, err = p.ds.Count(ctx, &model.Env{Project: name}, nil)
if err != nil {
return err
}
if count > 0 {
return bcode.ErrProjectDenyDeleteByEnvironment
}
users, _ := p.ListProjectUser(ctx, name, 0, 0)
for _, user := range users.Users {
err := p.DeleteProjectUser(ctx, name, user.UserName)
if err != nil {
return err
}
}
roles, _ := p.rbacUsecase.ListRole(ctx, name, 0, 0)
for _, role := range roles.Roles {
err := p.rbacUsecase.DeleteRole(ctx, name, role.Name)
if err != nil {
return err
}
}
permissions, _ := p.rbacUsecase.ListPermissions(ctx, name)
for _, perm := range permissions {
err := p.rbacUsecase.DeletePermission(ctx, name, perm.Name)
if err != nil {
return err
}
}
return p.ds.Delete(ctx, &model.Project{Name: name})
}
@@ -171,32 +304,191 @@ func (p *projectUsecaseImpl) CreateProject(ctx context.Context, req apisv1.Creat
if exist {
return nil, bcode.ErrProjectIsExist
}
owner := req.Owner
if owner == "" {
loginUserName, ok := ctx.Value(&apisv1.CtxKeyUser).(string)
if ok {
owner = loginUserName
}
}
var user = &model.User{Name: owner}
if owner != "" {
if err := p.ds.Get(ctx, user); err != nil {
return nil, bcode.ErrProjectOwnerIsNotExist
}
}
newProject := &model.Project{
Name: req.Name,
Description: req.Description,
Alias: req.Alias,
Owner: owner,
}
if err := p.ds.Add(ctx, newProject); err != nil {
return nil, err
}
return &apisv1.ProjectBase{
Name: newProject.Name,
Alias: newProject.Alias,
Description: newProject.Description,
CreateTime: newProject.CreateTime,
UpdateTime: newProject.UpdateTime,
}, nil
if err := p.rbacUsecase.InitDefaultRoleAndUsersForProject(ctx, newProject); err != nil {
log.Logger.Errorf("init default role and users for project failure %s", err.Error())
}
return ConvertProjectModel2Base(newProject, user), nil
}
func convertProjectModel2Base(project *model.Project) *apisv1.ProjectBase {
return &apisv1.ProjectBase{
// UpdateProject update project
func (p *projectUsecaseImpl) UpdateProject(ctx context.Context, projectName string, req apisv1.UpdateProjectRequest) (*apisv1.ProjectBase, error) {
project, err := p.GetProject(ctx, projectName)
if err != nil {
return nil, err
}
project.Alias = req.Alias
project.Description = req.Description
var user = &model.User{Name: req.Owner}
if req.Owner != "" {
if err := p.ds.Get(ctx, user); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return nil, bcode.ErrProjectOwnerIsNotExist
}
return nil, err
}
project.Owner = req.Owner
}
err = p.ds.Put(ctx, project)
if err != nil {
return nil, err
}
return ConvertProjectModel2Base(project, user), nil
}
func (p *projectUsecaseImpl) ListProjectUser(ctx context.Context, projectName string, page, pageSize int) (*apisv1.ListProjectUsersResponse, error) {
var projectUser = model.ProjectUser{
ProjectName: projectName,
}
entities, err := p.ds.List(ctx, &projectUser, &datastore.ListOptions{Page: page, PageSize: pageSize, SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}})
if err != nil {
return nil, err
}
var res apisv1.ListProjectUsersResponse
for _, entity := range entities {
res.Users = append(res.Users, ConvertProjectUserModel2Base(entity.(*model.ProjectUser)))
}
count, err := p.ds.Count(ctx, &projectUser, nil)
if err != nil {
return nil, err
}
res.Total = count
return &res, nil
}
func (p *projectUsecaseImpl) AddProjectUser(ctx context.Context, projectName string, req apisv1.AddProjectUserRequest) (*apisv1.ProjectUserBase, error) {
project, err := p.GetProject(ctx, projectName)
if err != nil {
return nil, err
}
// check user roles
for _, role := range req.UserRoles {
var projectUser = model.Role{
Name: role,
Project: projectName,
}
if err := p.ds.Get(ctx, &projectUser); err != nil {
return nil, bcode.ErrProjectRoleCheckFailure
}
if projectUser.Project != "" && projectUser.Project != projectName {
return nil, bcode.ErrProjectRoleCheckFailure
}
}
var projectUser = model.ProjectUser{
Username: req.UserName,
ProjectName: project.Name,
UserRoles: req.UserRoles,
}
if err := p.ds.Add(ctx, &projectUser); err != nil {
if errors.Is(err, datastore.ErrRecordExist) {
return nil, bcode.ErrProjectUserExist
}
return nil, err
}
return ConvertProjectUserModel2Base(&projectUser), nil
}
func (p *projectUsecaseImpl) DeleteProjectUser(ctx context.Context, projectName string, userName string) error {
project, err := p.GetProject(ctx, projectName)
if err != nil {
return err
}
var projectUser = model.ProjectUser{
Username: userName,
ProjectName: project.Name,
}
if err := p.ds.Delete(ctx, &projectUser); err != nil {
if errors.Is(err, datastore.ErrRecordExist) {
return bcode.ErrProjectUserExist
}
return err
}
return nil
}
func (p *projectUsecaseImpl) UpdateProjectUser(ctx context.Context, projectName string, userName string, req apisv1.UpdateProjectUserRequest) (*apisv1.ProjectUserBase, error) {
project, err := p.GetProject(ctx, projectName)
if err != nil {
return nil, err
}
// check user roles
for _, role := range req.UserRoles {
var projectUser = model.Role{
Name: role,
Project: projectName,
}
if err := p.ds.Get(ctx, &projectUser); err != nil {
return nil, bcode.ErrProjectRoleCheckFailure
}
if projectUser.Project != "" && projectUser.Project != projectName {
return nil, bcode.ErrProjectRoleCheckFailure
}
}
var projectUser = model.ProjectUser{
Username: userName,
ProjectName: project.Name,
}
if err := p.ds.Get(ctx, &projectUser); err != nil {
if errors.Is(err, datastore.ErrRecordExist) {
return nil, bcode.ErrProjectUserExist
}
return nil, err
}
projectUser.UserRoles = req.UserRoles
if err := p.ds.Put(ctx, &projectUser); err != nil {
return nil, err
}
return ConvertProjectUserModel2Base(&projectUser), nil
}
// ConvertProjectModel2Base convert project model to base struct
func ConvertProjectModel2Base(project *model.Project, owner *model.User) *apisv1.ProjectBase {
base := &apisv1.ProjectBase{
Name: project.Name,
Description: project.Description,
Alias: project.Alias,
CreateTime: project.CreateTime,
UpdateTime: project.UpdateTime,
Owner: apisv1.NameAlias{Name: project.Owner},
}
if owner != nil && owner.Name == project.Owner {
base.Owner = apisv1.NameAlias{Name: owner.Name, Alias: owner.Alias}
}
return base
}
// ConvertProjectUserModel2Base convert project user model to base struct
func ConvertProjectUserModel2Base(user *model.ProjectUser) *apisv1.ProjectUserBase {
base := &apisv1.ProjectUserBase{
UserName: user.Username,
UserRoles: user.UserRoles,
CreateTime: user.CreateTime,
UpdateTime: user.UpdateTime,
}
return base
}
+116 -7
View File
@@ -26,9 +26,11 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
velatypes "github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/util"
@@ -38,6 +40,7 @@ var _ = Describe("Test project usecase functions", func() {
var (
projectUsecase *projectUsecaseImpl
envImpl *envUsecaseImpl
userUsecase *userUsecaseImpl
targetImpl *targetUsecaseImpl
defaultNamespace = "project-default-ns1-test"
)
@@ -45,11 +48,13 @@ var _ = Describe("Test project usecase functions", func() {
ds, err := NewDatastore(datastore.Config{Type: "kubeapi", Database: "target-test-kubevela"})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
userUsecase = &userUsecaseImpl{ds: ds, k8sClient: k8sClient}
var ns = corev1.Namespace{}
ns.Name = defaultNamespace
err = k8sClient.Create(context.TODO(), &ns)
Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
projectUsecase = &projectUsecaseImpl{k8sClient: k8sClient, ds: ds}
projectUsecase = &projectUsecaseImpl{k8sClient: k8sClient, ds: ds, rbacUsecase: &rbacUsecaseImpl{ds: ds}}
pp, err := projectUsecase.ListProjects(context.TODO(), 0, 0)
Expect(err).Should(BeNil())
// reset all projects
@@ -57,24 +62,35 @@ var _ = Describe("Test project usecase functions", func() {
_ = projectUsecase.DeleteProject(context.TODO(), p.Name)
}
envImpl = &envUsecaseImpl{kubeClient: k8sClient, ds: ds}
envs, err := envImpl.ListEnvs(context.TODO(), 0, 0, apisv1.ListEnvOptions{})
envImpl = &envUsecaseImpl{kubeClient: k8sClient, ds: ds, projectUsecase: projectUsecase}
ctx := context.WithValue(context.TODO(), &apisv1.CtxKeyUser, "admin")
envs, err := envImpl.ListEnvs(ctx, 0, 0, apisv1.ListEnvOptions{})
Expect(err).Should(BeNil())
// reset all projects
for _, e := range envs.Envs {
_ = envImpl.DeleteEnv(context.TODO(), e.Name)
}
targetImpl = &targetUsecaseImpl{k8sClient: k8sClient, ds: ds}
targs, err := targetImpl.ListTargets(context.TODO(), 0, 0)
targets, err := targetImpl.ListTargets(context.TODO(), 0, 0, "")
Expect(err).Should(BeNil())
// reset all projects
for _, e := range targs.Targets {
_ = envImpl.DeleteEnv(context.TODO(), e.Name)
for _, t := range targets.Targets {
_ = targetImpl.DeleteTarget(context.TODO(), t.Name)
}
})
It("Test project initialize function", func() {
projectUsecase.initDefaultProjectEnvTarget(defaultNamespace)
// init admin user
var ns = corev1.Namespace{}
ns.Name = velatypes.DefaultKubeVelaNS
err := k8sClient.Create(context.TODO(), &ns)
Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
err = userUsecase.Init(context.TODO())
Expect(err).Should(BeNil())
// init default project
err = projectUsecase.InitDefaultProjectEnvTarget(context.WithValue(context.TODO(), &apisv1.CtxKeyUser, model.DefaultAdminUserName), defaultNamespace)
Expect(err).Should(BeNil())
By("test env created")
var namespace corev1.Namespace
Eventually(func() error {
@@ -134,4 +150,97 @@ var _ = Describe("Test project usecase functions", func() {
projectUsecase.DeleteProject(context.TODO(), "test-project")
})
It("Test Update project function", func() {
req := apisv1.CreateProjectRequest{
Name: "test-project",
Description: "this is a project description",
}
_, err := projectUsecase.CreateProject(context.TODO(), req)
Expect(err).Should(BeNil())
base, err := projectUsecase.UpdateProject(context.TODO(), "test-project", apisv1.UpdateProjectRequest{
Alias: "Change alias",
Description: "Change description",
Owner: "admin",
})
Expect(err).Should(BeNil())
Expect(base.Alias).Should(BeEquivalentTo("Change alias"))
Expect(base.Description).Should(BeEquivalentTo("Change description"))
Expect(base.Owner.Alias).Should(BeEquivalentTo("Administrator"))
_, err = projectUsecase.UpdateProject(context.TODO(), "test-project", apisv1.UpdateProjectRequest{
Alias: "Change alias",
Description: "Change description",
Owner: "admin-error",
})
Expect(err).Should(BeEquivalentTo(bcode.ErrProjectOwnerIsNotExist))
err = projectUsecase.DeleteProject(context.TODO(), "test-project")
Expect(err).Should(BeNil())
})
It("Test Create project user function", func() {
req := apisv1.CreateProjectRequest{
Name: "test-project",
Description: "this is a project description",
}
_, err := projectUsecase.CreateProject(context.TODO(), req)
Expect(err).Should(BeNil())
_, err = projectUsecase.AddProjectUser(context.TODO(), "test-project", apisv1.AddProjectUserRequest{
UserName: "admin",
UserRoles: []string{"project-admin"},
})
Expect(err).Should(BeNil())
})
It("Test Update project user function", func() {
req := apisv1.CreateProjectRequest{
Name: "test-project",
Description: "this is a project description",
}
_, err := projectUsecase.CreateProject(context.TODO(), req)
Expect(err).Should(BeNil())
_, err = projectUsecase.AddProjectUser(context.TODO(), "test-project", apisv1.AddProjectUserRequest{
UserName: "admin",
UserRoles: []string{"project-admin"},
})
Expect(err).Should(BeNil())
_, err = projectUsecase.UpdateProjectUser(context.TODO(), "test-project", "admin", apisv1.UpdateProjectUserRequest{
UserRoles: []string{"project-admin", "app-developer"},
})
Expect(err).Should(BeNil())
_, err = projectUsecase.UpdateProjectUser(context.TODO(), "test-project", "admin", apisv1.UpdateProjectUserRequest{
UserRoles: []string{"project-admin", "app-developer", "xxx"},
})
Expect(err).Should(BeEquivalentTo(bcode.ErrProjectRoleCheckFailure))
})
It("Test delete project user and delete project function", func() {
req := apisv1.CreateProjectRequest{
Name: "test-project",
Description: "this is a project description",
}
_, err := projectUsecase.CreateProject(context.TODO(), req)
Expect(err).Should(BeNil())
_, err = projectUsecase.AddProjectUser(context.TODO(), "test-project", apisv1.AddProjectUserRequest{
UserName: "admin",
UserRoles: []string{"project-admin"},
})
Expect(err).Should(BeNil())
err = projectUsecase.DeleteProjectUser(context.TODO(), "test-project", "admin")
Expect(err).Should(BeNil())
err = projectUsecase.DeleteProject(context.TODO(), "test-project")
Expect(err).Should(BeNil())
perms, err := projectUsecase.rbacUsecase.ListPermissions(context.TODO(), "test-project")
Expect(err).Should(BeNil())
Expect(len(perms)).Should(BeEquivalentTo(0))
roles, err := projectUsecase.rbacUsecase.ListRole(context.TODO(), "test-project", 0, 0)
Expect(err).Should(BeNil())
Expect(roles.Total).Should(BeEquivalentTo(0))
})
})
+913
View File
@@ -0,0 +1,913 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package usecase
import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"sync"
"github.com/emicklei/go-restful/v3"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
"github.com/oam-dev/kubevela/pkg/utils"
)
// resourceActions all register resources and actions
var resourceActions map[string][]string
var lock sync.Mutex
var reg = regexp.MustCompile(`(?U)\{.*\}`)
var defaultProjectPermissionTemplate = []*model.PermissionTemplate{
{
Name: "project-read",
Alias: "Project Read",
Resources: []string{"project:{projectName}"},
Actions: []string{"detail"},
Effect: "Allow",
Scope: "project",
},
{
Name: "app-management",
Alias: "App Management",
Resources: []string{"project:{projectName}/application:*/*", "definition:*"},
Actions: []string{"*"},
Effect: "Allow",
Scope: "project",
},
{
Name: "env-management",
Alias: "Environment Management",
Resources: []string{"project:{projectName}/environment:*"},
Actions: []string{"*"},
Effect: "Allow",
Scope: "project",
},
{
Name: "role-management",
Alias: "Role Management",
Resources: []string{"project:{projectName}/role:*", "project:{projectName}/projectUser:*", "project:{projectName}/permission:*"},
Actions: []string{"*"},
Effect: "Allow",
Scope: "project",
},
}
var defaultPlatformPermission = []*model.PermissionTemplate{
{
Name: "cluster-manage",
Alias: "Cluster Management",
Resources: []string{"cluster:*/*"},
Actions: []string{"*"},
Effect: "Allow",
Scope: "platform",
},
{
Name: "project-management",
Alias: "Project Management",
Resources: []string{"project:*"},
Actions: []string{"*"},
Effect: "Allow",
Scope: "platform",
},
{
Name: "addon-management",
Alias: "Addon Management",
Resources: []string{"addon:*", "addonRegistry:*"},
Actions: []string{"*"},
Effect: "Allow",
Scope: "platform",
},
{
Name: "target-manage",
Alias: "Target Management",
Resources: []string{"target:*", "cluster:*/namespace:*"},
Actions: []string{"*"},
Effect: "Allow",
Scope: "platform",
},
{
Name: "user-manage",
Alias: "User Management",
Resources: []string{"user:*"},
Actions: []string{"*"},
Effect: "Allow",
Scope: "platform",
},
{
Name: "role-manage",
Alias: "Platform Role Management",
Resources: []string{"role:*", "permission:*"},
Actions: []string{"*"},
Effect: "Allow",
Scope: "platform",
},
{
Name: "admin",
Alias: "Admin",
Resources: []string{"*"},
Actions: []string{"*"},
Effect: "Allow",
Scope: "platform",
},
}
// ResourceMaps all resources definition for RBAC
var ResourceMaps = map[string]resourceMetadata{
"project": {
subResources: map[string]resourceMetadata{
"application": {
pathName: "appName",
subResources: map[string]resourceMetadata{
"component": {
subResources: map[string]resourceMetadata{
"trait": {
pathName: "traitType",
},
},
pathName: "compName",
},
"workflow": {
subResources: map[string]resourceMetadata{
"record": {
pathName: "record",
},
},
pathName: "workflowName",
},
"policy": {
pathName: "policyName",
},
"revision": {
pathName: "revision",
},
"envBinding": {
pathName: "envName",
},
"trigger": {},
},
},
"environment": {
pathName: "envName",
},
"workflow": {
pathName: "workflowName",
},
"role": {
pathName: "roleName",
},
"permission": {},
"projectUser": {
pathName: "userName",
},
"applicationTemplate": {},
},
pathName: "projectName",
},
"cluster": {
pathName: "clusterName",
subResources: map[string]resourceMetadata{
"namespace": {},
},
},
"addon": {
pathName: "addonName",
},
"addonRegistry": {
pathName: "addonRegName",
},
"target": {
pathName: "targetName",
},
"user": {
pathName: "userName",
},
"role": {},
"permission": {},
"systemSetting": {},
"definition": {},
}
var existResourcePaths = convert(ResourceMaps)
type resourceMetadata struct {
subResources map[string]resourceMetadata
pathName string
}
func checkResourcePath(resource string) (string, error) {
if sub, exist := ResourceMaps[resource]; exist {
if sub.pathName != "" {
return fmt.Sprintf("%s:{%s}", resource, sub.pathName), nil
}
return fmt.Sprintf("%s:*", resource), nil
}
path := ""
exist := 0
lastResourceName := resource[strings.LastIndex(resource, "/")+1:]
for key, erp := range existResourcePaths {
allMatchIndex := strings.Index(key, fmt.Sprintf("/%s/", resource))
index := strings.Index(erp, fmt.Sprintf("/%s:", lastResourceName))
if index > -1 && allMatchIndex > -1 {
pre := erp[:index+len(lastResourceName)+2]
next := strings.Replace(erp, pre, "", 1)
nameIndex := strings.Index(next, "/")
if nameIndex > -1 {
pre += next[:nameIndex]
}
if pre != path {
exist++
}
path = pre
}
}
path = strings.TrimSuffix(path, "/")
path = strings.TrimPrefix(path, "/")
if exist == 1 {
return path, nil
}
if exist > 1 {
return path, fmt.Errorf("the resource name %s is not unique", resource)
}
return path, fmt.Errorf("there is no resource %s", resource)
}
func convert(sources map[string]resourceMetadata) map[string]string {
list := make(map[string]string)
for k, v := range sources {
if len(v.subResources) > 0 {
for sub, subWithPathName := range convert(v.subResources) {
if subWithPathName != "" {
withPathname := fmt.Sprintf("/%s:*%s", k, subWithPathName)
if v.pathName != "" {
withPathname = fmt.Sprintf("/%s:{%s}%s", k, v.pathName, subWithPathName)
}
list[fmt.Sprintf("/%s%s", k, sub)] = withPathname
}
}
}
withPathname := fmt.Sprintf("/%s:*/", k)
if v.pathName != "" {
withPathname = fmt.Sprintf("/%s:{%s}/", k, v.pathName)
}
list[fmt.Sprintf("/%s/", k)] = withPathname
}
return list
}
// registerResourceAction register resource actions
func registerResourceAction(resource string, actions ...string) {
lock.Lock()
defer lock.Unlock()
if resourceActions == nil {
resourceActions = make(map[string][]string)
}
path, err := checkResourcePath(resource)
if err != nil {
panic(fmt.Sprintf("resource %s is not exist", resource))
}
resource = path
if _, exist := resourceActions[resource]; exist {
for _, action := range actions {
if !utils.StringsContain(resourceActions[resource], action) {
resourceActions[resource] = append(resourceActions[resource], action)
}
}
} else {
resourceActions[resource] = actions
}
}
type rbacUsecaseImpl struct {
ds datastore.DataStore
}
// RBACUsecase implement RBAC-related business logic.
type RBACUsecase interface {
CheckPerm(resource string, actions ...string) func(req *restful.Request, res *restful.Response, chain *restful.FilterChain)
GetUserPermissions(ctx context.Context, user *model.User, projectName string, withPlatform bool) ([]*model.Permission, error)
CreateRole(ctx context.Context, projectName string, req apisv1.CreateRoleRequest) (*apisv1.RoleBase, error)
DeleteRole(ctx context.Context, projectName, roleName string) error
UpdateRole(ctx context.Context, projectName, roleName string, req apisv1.UpdateRoleRequest) (*apisv1.RoleBase, error)
ListRole(ctx context.Context, projectName string, page, pageSize int) (*apisv1.ListRolesResponse, error)
ListPermissionTemplate(ctx context.Context, projectName string) ([]apisv1.PermissionTemplateBase, error)
ListPermissions(ctx context.Context, projectName string) ([]apisv1.PermissionBase, error)
DeletePermission(ctx context.Context, projectName, permName string) error
InitDefaultRoleAndUsersForProject(ctx context.Context, project *model.Project) error
Init(ctx context.Context) error
}
// NewRBACUsecase is the usecase service of RBAC
func NewRBACUsecase(ds datastore.DataStore) RBACUsecase {
rbacUsecase := &rbacUsecaseImpl{
ds: ds,
}
return rbacUsecase
}
func (p *rbacUsecaseImpl) Init(ctx context.Context) error {
count, _ := p.ds.Count(ctx, &model.Permission{}, &datastore.FilterOptions{
IsNotExist: []datastore.IsNotExistQueryOption{
{
Key: "project",
},
},
})
if count > 0 {
return nil
}
var batchData []datastore.Entity
for _, policy := range defaultPlatformPermission {
batchData = append(batchData, &model.Permission{
Name: policy.Name,
Alias: policy.Alias,
Resources: policy.Resources,
Actions: policy.Actions,
Effect: policy.Effect,
})
}
batchData = append(batchData, &model.Role{
Name: "admin",
Alias: "Admin",
Permissions: []string{"admin"},
})
if err := p.ds.BatchAdd(context.Background(), batchData); err != nil {
return fmt.Errorf("init the platform perm policies failure %w", err)
}
return nil
}
// GetUserPermissions get user permission policies, if projectName is empty, will only get the platform permission policies
func (p *rbacUsecaseImpl) GetUserPermissions(ctx context.Context, user *model.User, projectName string, withPlatform bool) ([]*model.Permission, error) {
var permissionNames []string
var perms []*model.Permission
if withPlatform && len(user.UserRoles) > 0 {
entities, err := p.ds.List(ctx, &model.Role{}, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{
In: []datastore.InQueryOption{
{
Key: "name",
Values: user.UserRoles,
},
},
IsNotExist: []datastore.IsNotExistQueryOption{
{
Key: "project",
},
},
}})
if err != nil {
return nil, err
}
for _, entity := range entities {
permissionNames = append(permissionNames, entity.(*model.Role).Permissions...)
}
perms, err = p.listPermPolices(ctx, "", permissionNames)
if err != nil {
return nil, err
}
}
if projectName != "" {
var projectUser = model.ProjectUser{
ProjectName: projectName,
Username: user.Name,
}
var roles []string
if err := p.ds.Get(ctx, &projectUser); err == nil {
roles = append(roles, projectUser.UserRoles...)
}
if len(roles) > 0 {
entities, err := p.ds.List(ctx, &model.Role{Project: projectName}, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{In: []datastore.InQueryOption{
{
Key: "name",
Values: roles,
},
}}})
if err != nil {
return nil, err
}
for _, entity := range entities {
permissionNames = append(permissionNames, entity.(*model.Role).Permissions...)
}
projectPerms, err := p.listPermPolices(ctx, projectName, permissionNames)
if err != nil {
return nil, err
}
perms = append(perms, projectPerms...)
}
}
return perms, nil
}
func (p *rbacUsecaseImpl) UpdatePermission(ctx context.Context, projetName string, permissionName string, req *apisv1.UpdatePermissionRequest) (*apisv1.PermissionBase, error) {
perm := &model.Permission{
Project: projetName,
Name: permissionName,
}
err := p.ds.Get(ctx, perm)
if err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return nil, bcode.ErrPermissionNotExist
}
}
//TODO: check req validate
perm.Actions = req.Actions
perm.Alias = req.Alias
perm.Resources = req.Resources
perm.Effect = req.Effect
if err := p.ds.Put(ctx, perm); err != nil {
return nil, err
}
return &apisv1.PermissionBase{
Name: perm.Name,
Alias: perm.Alias,
Resources: perm.Resources,
Actions: perm.Actions,
Effect: perm.Effect,
CreateTime: perm.CreateTime,
UpdateTime: perm.UpdateTime,
}, nil
}
func (p *rbacUsecaseImpl) listPermPolices(ctx context.Context, projectName string, permissionNames []string) ([]*model.Permission, error) {
if len(permissionNames) == 0 {
return []*model.Permission{}, nil
}
filter := datastore.FilterOptions{In: []datastore.InQueryOption{
{
Key: "name",
Values: permissionNames,
},
}}
if projectName == "" {
filter.IsNotExist = append(filter.IsNotExist, datastore.IsNotExistQueryOption{
Key: "project",
})
}
permEntities, err := p.ds.List(ctx, &model.Permission{Project: projectName}, &datastore.ListOptions{FilterOptions: filter})
if err != nil {
return nil, err
}
var perms []*model.Permission
for _, entity := range permEntities {
perms = append(perms, entity.(*model.Permission))
}
return perms, nil
}
func (p *rbacUsecaseImpl) CheckPerm(resource string, actions ...string) func(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
registerResourceAction(resource, actions...)
f := func(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
// get login user info
userName, ok := req.Request.Context().Value(&apisv1.CtxKeyUser).(string)
if !ok {
bcode.ReturnError(req, res, bcode.ErrUnauthorized)
return
}
user := &model.User{Name: userName}
if err := p.ds.Get(req.Request.Context(), user); err != nil {
bcode.ReturnError(req, res, bcode.ErrUnauthorized)
return
}
path, err := checkResourcePath(resource)
if err != nil {
log.Logger.Errorf("check resource path failure %s", err.Error())
bcode.ReturnError(req, res, bcode.ErrForbidden)
return
}
// multiple method for get the project name.
getProjectName := func() string {
if value := req.PathParameter("projectName"); value != "" {
return value
}
if value := req.QueryParameter("project"); value != "" {
return value
}
if value := req.QueryParameter("projectName"); value != "" {
return value
}
if appName := req.PathParameter(ResourceMaps["project"].subResources["application"].pathName); appName != "" {
app := &model.Application{Name: appName}
if err := p.ds.Get(req.Request.Context(), app); err == nil {
return app.Project
}
}
if envName := req.PathParameter(ResourceMaps["project"].subResources["environment"].pathName); envName != "" {
env := &model.Env{Name: envName}
if err := p.ds.Get(req.Request.Context(), env); err == nil {
return env.Project
}
}
return ""
}
ra := &RequestResourceAction{}
ra.SetResourceWithName(path, func(name string) string {
if name == ResourceMaps["project"].pathName {
return getProjectName()
}
return req.PathParameter(name)
})
// get user's perm list.
projectName := getProjectName()
permissions, err := p.GetUserPermissions(req.Request.Context(), user, projectName, true)
if err != nil {
log.Logger.Errorf("get user's perm policies failure %s, user is %s", err.Error(), user.Name)
bcode.ReturnError(req, res, bcode.ErrForbidden)
return
}
if !ra.Match(permissions) {
bcode.ReturnError(req, res, bcode.ErrForbidden)
return
}
chain.ProcessFilter(req, res)
}
return f
}
func (p *rbacUsecaseImpl) CreateRole(ctx context.Context, projectName string, req apisv1.CreateRoleRequest) (*apisv1.RoleBase, error) {
if projectName != "" {
var project = model.Project{
Name: projectName,
}
if err := p.ds.Get(ctx, &project); err != nil {
return nil, bcode.ErrProjectIsNotExist
}
}
if len(req.Permissions) == 0 {
return nil, bcode.ErrRolePermissionCheckFailure
}
policies, err := p.listPermPolices(ctx, projectName, req.Permissions)
if err != nil || len(policies) != len(req.Permissions) {
return nil, bcode.ErrRolePermissionCheckFailure
}
var role = model.Role{
Name: req.Name,
Alias: req.Alias,
Project: projectName,
Permissions: req.Permissions,
}
if err := p.ds.Add(ctx, &role); err != nil {
if errors.Is(err, datastore.ErrRecordExist) {
return nil, bcode.ErrRoleIsExist
}
return nil, err
}
return ConvertRole2Model(&role, policies), nil
}
func (p *rbacUsecaseImpl) DeleteRole(ctx context.Context, projectName, roleName string) error {
var role = model.Role{
Name: roleName,
Project: projectName,
}
if err := p.ds.Delete(ctx, &role); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return bcode.ErrRoleIsNotExist
}
return err
}
return nil
}
func (p *rbacUsecaseImpl) DeletePermission(ctx context.Context, projectName, permName string) error {
var perm = model.Permission{
Name: permName,
Project: projectName,
}
if err := p.ds.Delete(ctx, &perm); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return bcode.ErrRoleIsNotExist
}
return err
}
return nil
}
func (p *rbacUsecaseImpl) UpdateRole(ctx context.Context, projectName, roleName string, req apisv1.UpdateRoleRequest) (*apisv1.RoleBase, error) {
if projectName != "" {
var project = model.Project{
Name: projectName,
}
if err := p.ds.Get(ctx, &project); err != nil {
return nil, bcode.ErrProjectIsNotExist
}
}
if len(req.Permissions) == 0 {
return nil, bcode.ErrRolePermissionCheckFailure
}
policies, err := p.listPermPolices(ctx, projectName, req.Permissions)
if err != nil || len(policies) != len(req.Permissions) {
return nil, bcode.ErrRolePermissionCheckFailure
}
var role = model.Role{
Name: roleName,
Project: projectName,
}
if err := p.ds.Get(ctx, &role); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return nil, bcode.ErrRoleIsNotExist
}
return nil, err
}
role.Alias = req.Alias
role.Permissions = req.Permissions
if err := p.ds.Put(ctx, &role); err != nil {
return nil, err
}
return ConvertRole2Model(&role, policies), nil
}
func (p *rbacUsecaseImpl) ListRole(ctx context.Context, projectName string, page, pageSize int) (*apisv1.ListRolesResponse, error) {
var role = model.Role{
Project: projectName,
}
var filter datastore.FilterOptions
if projectName == "" {
filter.IsNotExist = append(filter.IsNotExist, datastore.IsNotExistQueryOption{
Key: "project",
})
}
entities, err := p.ds.List(ctx, &role, &datastore.ListOptions{FilterOptions: filter, Page: page, PageSize: pageSize, SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}})
if err != nil {
return nil, err
}
var policySet = make(map[string]string)
for _, entity := range entities {
for _, p := range entity.(*model.Role).Permissions {
policySet[p] = p
}
}
policies, err := p.listPermPolices(ctx, projectName, utils.MapKey2Array(policySet))
if err != nil {
log.Logger.Errorf("list perm policies failure %s", err.Error())
}
var policyMap = make(map[string]*model.Permission)
for i, policy := range policies {
policyMap[policy.Name] = policies[i]
}
var res apisv1.ListRolesResponse
for _, entity := range entities {
role := entity.(*model.Role)
var rolePolicies []*model.Permission
for _, perm := range role.Permissions {
rolePolicies = append(rolePolicies, policyMap[perm])
}
res.Roles = append(res.Roles, ConvertRole2Model(entity.(*model.Role), rolePolicies))
}
count, err := p.ds.Count(ctx, &role, &filter)
if err != nil {
return nil, err
}
res.Total = count
return &res, nil
}
// ListPermissionTemplate TODO:
func (p *rbacUsecaseImpl) ListPermissionTemplate(ctx context.Context, projectName string) ([]apisv1.PermissionTemplateBase, error) {
return nil, nil
}
func (p *rbacUsecaseImpl) ListPermissions(ctx context.Context, projectName string) ([]apisv1.PermissionBase, error) {
var filter datastore.FilterOptions
if projectName == "" {
filter.IsNotExist = append(filter.IsNotExist, datastore.IsNotExistQueryOption{
Key: "project",
})
}
permEntities, err := p.ds.List(ctx, &model.Permission{Project: projectName}, &datastore.ListOptions{FilterOptions: filter})
if err != nil {
return nil, err
}
var perms []apisv1.PermissionBase
for _, entity := range permEntities {
perm := entity.(*model.Permission)
perms = append(perms, apisv1.PermissionBase{
Name: perm.Name,
Alias: perm.Alias,
Resources: perm.Resources,
Actions: perm.Actions,
Effect: perm.Effect,
CreateTime: perm.CreateTime,
UpdateTime: perm.UpdateTime,
})
}
return perms, nil
}
func (p *rbacUsecaseImpl) InitDefaultRoleAndUsersForProject(ctx context.Context, project *model.Project) error {
var batchData []datastore.Entity
for _, permissionTemp := range defaultProjectPermissionTemplate {
var rra = RequestResourceAction{}
var formatedResource []string
for _, resource := range permissionTemp.Resources {
rra.SetResourceWithName(resource, func(name string) string {
if name == ResourceMaps["project"].pathName {
return project.Name
}
return ""
})
formatedResource = append(formatedResource, rra.GetResource().String())
}
batchData = append(batchData, &model.Permission{
Name: permissionTemp.Name,
Alias: permissionTemp.Alias,
Project: project.Name,
Resources: formatedResource,
Actions: permissionTemp.Actions,
Effect: permissionTemp.Effect,
})
}
batchData = append(batchData, &model.Role{
Name: "app-developer",
Alias: "App Developer",
Permissions: []string{"project-read", "app-management", "env-management"},
Project: project.Name,
}, &model.Role{
Name: "project-admin",
Alias: "Project Admin",
Permissions: []string{"project-read", "app-management", "env-management", "role-management"},
Project: project.Name,
})
if project.Owner != "" {
var projectUser = &model.ProjectUser{
ProjectName: project.Name,
UserRoles: []string{"project-admin"},
Username: project.Owner,
}
batchData = append(batchData, projectUser)
}
return p.ds.BatchAdd(ctx, batchData)
}
// ConvertRole2Model convert role model to role base struct
func ConvertRole2Model(role *model.Role, policies []*model.Permission) *apisv1.RoleBase {
return &apisv1.RoleBase{
CreateTime: role.CreateTime,
UpdateTime: role.UpdateTime,
Name: role.Name,
Alias: role.Alias,
Permissions: func() (list []apisv1.NameAlias) {
for _, policy := range policies {
if policy != nil {
list = append(list, apisv1.NameAlias{Name: policy.Name, Alias: policy.Alias})
}
}
return
}(),
}
}
// ResourceName it is similar to ARNs
// <type>:<value>/<type>:<value>
type ResourceName struct {
Type string
Value string
Next *ResourceName
}
// ParseResourceName parse string to ResourceName
func ParseResourceName(resource string) *ResourceName {
RNs := strings.Split(resource, "/")
var resourceName = ResourceName{}
var current = &resourceName
for _, rn := range RNs {
rnData := strings.Split(rn, ":")
if len(rnData) == 2 {
current.Type = rnData[0]
current.Value = rnData[1]
}
if len(rnData) == 1 {
current.Type = rnData[0]
current.Value = "*"
}
var next = &ResourceName{}
current.Next = next
current = next
}
return &resourceName
}
// Match the resource types same with target and resource value include
// target resource means request resources
func (r *ResourceName) Match(target *ResourceName) bool {
current := r
currentTarget := target
for current != nil && current.Type != "" {
if current.Type == "*" {
return true
}
if currentTarget == nil || currentTarget.Type == "" {
return false
}
if current.Type != currentTarget.Type {
return false
}
if current.Value != currentTarget.Value && current.Value != "*" {
return false
}
current = current.Next
currentTarget = currentTarget.Next
}
if currentTarget != nil && currentTarget.Type != "" {
return false
}
return true
}
func (r *ResourceName) String() string {
strBuilder := &strings.Builder{}
current := r
for current != nil && current.Type != "" {
strBuilder.WriteString(fmt.Sprintf("%s:%s/", current.Type, current.Value))
current = current.Next
}
return strings.TrimSuffix(strBuilder.String(), "/")
}
// RequestResourceAction resource permission boundary
type RequestResourceAction struct {
resource *ResourceName
actions []string
}
// SetResourceWithName format resource and assign a value from path parameter
func (r *RequestResourceAction) SetResourceWithName(resource string, pathParameter func(name string) string) {
resultKey := reg.FindAllString(resource, -1)
for _, sourcekey := range resultKey {
key := sourcekey[1 : len(sourcekey)-1]
value := pathParameter(key)
if value == "" {
value = "*"
}
resource = strings.Replace(resource, sourcekey, value, 1)
}
r.resource = ParseResourceName(resource)
}
// GetResource return the resource after be formated
func (r *RequestResourceAction) GetResource() *ResourceName {
return r.resource
}
// SetActions set request actions
func (r *RequestResourceAction) SetActions(actions []string) {
r.actions = actions
}
func (r *RequestResourceAction) match(policy *model.Permission) bool {
// match actions, the policy actions will include the actions of request
if !utils.SliceIncludeSlice(policy.Actions, r.actions) && !utils.StringsContain(policy.Actions, "*") {
return false
}
// match resources
for _, resource := range policy.Resources {
resourceName := ParseResourceName(resource)
if resourceName.Match(r.resource) {
return true
}
}
return false
}
// Match determines whether the request resources and actions matches the user permission set.
func (r *RequestResourceAction) Match(policies []*model.Permission) bool {
for _, policy := range policies {
if strings.EqualFold(policy.Effect, "deny") {
if r.match(policy) {
return false
}
}
}
for _, policy := range policies {
if strings.EqualFold(policy.Effect, "allow") || policy.Effect == "" {
if r.match(policy) {
return true
}
}
}
return false
}
+272
View File
@@ -0,0 +1,272 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package usecase
import (
"context"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/emicklei/go-restful/v3"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/assert"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
)
var _ = Describe("Test rbac service", func() {
var ds datastore.DataStore
BeforeEach(func() {
var err error
ds, err = NewDatastore(datastore.Config{Type: "kubeapi", Database: "rbac-test-kubevela"})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
})
It("Test check resource", func() {
path, err := checkResourcePath("project")
Expect(err).Should(BeNil())
Expect(path).Should(BeEquivalentTo("project:{projectName}"))
path, err = checkResourcePath("application")
Expect(err).Should(BeNil())
Expect(path).Should(BeEquivalentTo("project:{projectName}/application:{appName}"))
_, err = checkResourcePath("applications")
Expect(err).ShouldNot(BeNil())
_, err = checkResourcePath("project/component")
Expect(err).ShouldNot(BeNil())
_, err = checkResourcePath("workflow")
Expect(err).ShouldNot(BeNil())
path, err = checkResourcePath("project/application/workflow")
Expect(err).Should(BeNil())
Expect(path).Should(BeEquivalentTo("project:{projectName}/application:{appName}/workflow:{workflowName}"))
path, err = checkResourcePath("project/workflow")
Expect(err).Should(BeNil())
Expect(path).Should(BeEquivalentTo("project:{projectName}/workflow:{workflowName}"))
path, err = checkResourcePath("component")
Expect(err).Should(BeNil())
Expect(path).Should(BeEquivalentTo("project:{projectName}/application:{appName}/component:{compName}"))
path, err = checkResourcePath("role")
Expect(err).Should(BeNil())
Expect(path).Should(BeEquivalentTo("role:*"))
})
It("Test resource action", func() {
ra := &RequestResourceAction{}
ra.SetResourceWithName("project:{projectName}/workflow:{empty}", testPathParameter)
Expect(ra.GetResource()).ShouldNot(BeNil())
Expect(ra.GetResource().Value).Should(BeEquivalentTo("projectName"))
Expect(ra.GetResource().Next).ShouldNot(BeNil())
Expect(ra.GetResource().Next.Value).Should(BeEquivalentTo("*"))
Expect(ra.GetResource().String()).Should(BeEquivalentTo("project:projectName/workflow:*"))
})
It("Test init and list platform permissions", func() {
rbacUsecase := rbacUsecaseImpl{ds: ds}
err := rbacUsecase.Init(context.TODO())
Expect(err).Should(BeNil())
policies, err := rbacUsecase.ListPermissions(context.TODO(), "")
Expect(err).Should(BeNil())
Expect(len(policies)).Should(BeEquivalentTo(int64(7)))
})
It("Test checkPerm by admin user", func() {
err := ds.Add(context.TODO(), &model.User{Name: "admin", UserRoles: []string{"admin"}})
Expect(err).Should(BeNil())
rbac := rbacUsecaseImpl{ds: ds}
req := &http.Request{}
req = req.WithContext(context.WithValue(req.Context(), &apisv1.CtxKeyUser, "admin"))
res := &restful.Response{}
pass := false
filter := &restful.FilterChain{
Target: restful.RouteFunction(func(req *restful.Request, res *restful.Response) {
pass = true
}),
}
rbac.CheckPerm("cluster", "create")(restful.NewRequest(req), res, filter)
Expect(pass).Should(BeTrue())
pass = false
rbac.CheckPerm("role", "list")(restful.NewRequest(req), res, filter)
Expect(pass).Should(BeTrue())
})
It("Test checkPerm by dev user", func() {
var projectName = "test-app-project"
err := ds.Add(context.TODO(), &model.User{Name: "dev"})
Expect(err).Should(BeNil())
err = ds.Add(context.TODO(), &model.Project{Name: projectName})
Expect(err).Should(BeNil())
err = ds.Add(context.TODO(), &model.ProjectUser{Username: "dev", ProjectName: projectName, UserRoles: []string{"application-admin"}})
Expect(err).Should(BeNil())
err = ds.Add(context.TODO(), &model.Role{Project: projectName, Name: "application-admin", Permissions: []string{"application-manage"}})
Expect(err).Should(BeNil())
err = ds.Add(context.TODO(), &model.Permission{Project: projectName, Name: "application-manage", Resources: []string{"project:test-app-project/application:*"}, Actions: []string{"*"}})
Expect(err).Should(BeNil())
rbac := rbacUsecaseImpl{ds: ds}
header := http.Header{}
header.Set("Accept", "application/json")
header.Set("Content-Type", "application/json")
req := &http.Request{
Header: header,
}
req = req.WithContext(context.WithValue(req.Context(), &apisv1.CtxKeyUser, "dev"))
req.Form = url.Values{}
req.Form.Set("project", projectName)
record := httptest.NewRecorder()
res := restful.NewResponse(record)
res.SetRequestAccepts("application/json")
pass := false
filter := &restful.FilterChain{
Target: restful.RouteFunction(func(req *restful.Request, res *restful.Response) {
pass = true
}),
}
rbac.CheckPerm("cluster", "create")(restful.NewRequest(req), res, filter)
Expect(pass).Should(BeFalse())
Expect(res.StatusCode()).Should(Equal(int(bcode.ErrForbidden.HTTPCode)))
rbac.CheckPerm("component", "list")(restful.NewRequest(req), res, filter)
Expect(res.StatusCode()).Should(Equal(int(bcode.ErrForbidden.HTTPCode)))
// add list application permission to role
// err = ds.Add(context.TODO(), &model.Permission{Project: projectName, Name: "application-list", Resources: []string{"project:*/application:*"}, Actions: []string{"list"}})
// Expect(err).Should(BeNil())
// _, err = rbac.UpdateRole(context.TODO(), projectName, "application-admin", apisv1.UpdateRoleRequest{
// Permissions: []string{"application-list", "application-manage"},
// })
// Expect(err).Should(BeNil())
// req.Form.Del("project")
// pass = false
// rbac.CheckPerm("application", "list")(restful.NewRequest(req), res, filter)
// Expect(pass).Should(BeTrue())
})
It("Test initDefaultRoleAndUsersForProject", func() {
rbacUsecase := rbacUsecaseImpl{ds: ds}
err := ds.Add(context.TODO(), &model.User{Name: "test-user"})
Expect(err).Should(BeNil())
err = ds.Add(context.TODO(), &model.Project{Name: "init-test", Owner: "test-user"})
Expect(err).Should(BeNil())
err = rbacUsecase.InitDefaultRoleAndUsersForProject(context.TODO(), &model.Project{Name: "init-test"})
Expect(err).Should(BeNil())
roles, err := rbacUsecase.ListRole(context.TODO(), "init-test", 0, 0)
Expect(err).Should(BeNil())
Expect(roles.Total).Should(BeEquivalentTo(int64(2)))
policies, err := rbacUsecase.ListPermissions(context.TODO(), "init-test")
Expect(err).Should(BeNil())
Expect(len(policies)).Should(BeEquivalentTo(int64(4)))
})
It("Test UpdatePermission", func() {
rbacUsecase := rbacUsecaseImpl{ds: ds}
base, err := rbacUsecase.UpdatePermission(context.TODO(), "test-app-project", "application-manage", &apisv1.UpdatePermissionRequest{
Resources: []string{"project:{projectName}/application:*/*"},
Actions: []string{"*"},
Alias: "App Management Update",
})
Expect(err).Should(BeNil())
Expect(base.Alias).Should(BeEquivalentTo("App Management Update"))
})
})
func testPathParameter(name string) string {
if name == "empty" {
return ""
}
return name
}
func TestRequestResourceAction(t *testing.T) {
ra := &RequestResourceAction{}
ra.SetResourceWithName("project:{projectName}/workflow:{empty}", testPathParameter)
assert.NotEqual(t, ra.GetResource(), nil)
assert.Equal(t, ra.GetResource().Value, "projectName")
assert.NotEqual(t, ra.GetResource().Next, nil)
assert.Equal(t, ra.GetResource().Next.Value, "*")
ra2 := &RequestResourceAction{}
ra2.SetResourceWithName("project:{empty}/application:{empty}", testPathParameter)
assert.Equal(t, ra2.GetResource().String(), "project:*/application:*")
}
func TestRequestResourceActionMatch(t *testing.T) {
ra := &RequestResourceAction{}
ra.SetResourceWithName("project:{projectName}/workflow:{empty}", testPathParameter)
ra.SetActions([]string{"create"})
assert.Equal(t, ra.Match([]*model.Permission{{Resources: []string{"project:*/workflow:*"}, Actions: []string{"*"}}}), true)
assert.Equal(t, ra.Match([]*model.Permission{{Resources: []string{"project:ddd/workflow:*"}, Actions: []string{"create"}}}), false)
assert.Equal(t, ra.Match([]*model.Permission{{Resources: []string{"project:projectName/workflow:*"}, Actions: []string{"create"}}}), true)
assert.Equal(t, ra.Match([]*model.Permission{{Resources: []string{"project:projectName/workflow:*"}, Actions: []string{"create"}, Effect: "Deny"}}), false)
ra2 := &RequestResourceAction{}
ra2.SetResourceWithName("project:{projectName}/application:{app1}/component:{empty}", testPathParameter)
ra2.SetActions([]string{"delete"})
assert.Equal(t, ra2.Match([]*model.Permission{{Resources: []string{"project:*/application:app1/component:*"}, Actions: []string{"*"}}}), true)
assert.Equal(t, ra2.Match([]*model.Permission{{Resources: []string{"project:*/application:app1/component:*"}, Actions: []string{"list", "delete"}}}), true)
assert.Equal(t, ra2.Match([]*model.Permission{{Resources: []string{"project:*", "project:*/application:app1/component:*"}, Actions: []string{"list", "delete"}}}), true)
assert.Equal(t, ra2.Match([]*model.Permission{{Resources: []string{"project:*/application:app1/component:*"}, Actions: []string{"list", "detail"}}}), false)
assert.Equal(t, ra2.Match([]*model.Permission{{Resources: []string{"*"}, Actions: []string{"*"}}}), true)
assert.Equal(t, ra2.Match([]*model.Permission{{Resources: []string{"*"}, Actions: []string{"*"}}, {Actions: []string{"*"}, Resources: []string{"project:*/application:app1/component:*"}, Effect: "Deny"}}), false)
assert.Equal(t, ra2.Match([]*model.Permission{{Resources: []string{"project:projectName/application:*/*"}, Actions: []string{"*"}}}), true)
ra3 := &RequestResourceAction{}
ra3.SetResourceWithName("project:test-123", testPathParameter)
ra3.SetActions([]string{"detail"})
assert.Equal(t, ra3.Match([]*model.Permission{{Resources: []string{"*"}, Actions: []string{"*"}, Effect: "Allow"}}), true)
ra4 := &RequestResourceAction{}
ra4.SetResourceWithName("role:*", testPathParameter)
ra4.SetActions([]string{"list"})
assert.Equal(t, ra4.Match([]*model.Permission{{Resources: []string{"*"}, Actions: []string{"*"}, Effect: "Allow"}}), true)
ra5 := &RequestResourceAction{}
ra5.SetResourceWithName("project:*/application:*", testPathParameter)
ra5.SetActions([]string{"list"})
assert.Equal(t, ra5.Match([]*model.Permission{{Resources: []string{"project:*/application:*"}, Actions: []string{"list"}, Effect: "Allow"}}), true)
}
func TestRegisterResourceAction(t *testing.T) {
registerResourceAction("role", "list")
registerResourceAction("project/role", "list")
t.Log(resourceActions)
}
+3 -13
View File
@@ -30,7 +30,6 @@ import (
// SystemInfoUsecase is usecase for systemInfoCollection
type SystemInfoUsecase interface {
GetSystemInfo(ctx context.Context) (*v1.SystemInfoResponse, error)
DeleteSystemInfo(ctx context.Context) error
UpdateSystemInfo(ctx context.Context, sysInfo v1.SystemInfoRequest) (*v1.SystemInfoResponse, error)
}
@@ -52,6 +51,9 @@ func (u systemInfoUsecaseImpl) GetSystemInfo(ctx context.Context) (*v1.SystemInf
}
if len(entities) != 0 {
info := entities[0].(*model.SystemInfo)
if info.LoginType == "" {
info.LoginType = model.LoginTypeLocal
}
return &v1.SystemInfoResponse{SystemInfo: *info, SystemVersion: v1.SystemVersion{VelaVersion: version.VelaVersion, GitVersion: version.GitRevision}}, nil
}
installID := rand.String(16)
@@ -83,15 +85,3 @@ func (u systemInfoUsecaseImpl) UpdateSystemInfo(ctx context.Context, sysInfo v1.
}
return &v1.SystemInfoResponse{SystemInfo: modifiedInfo, SystemVersion: v1.SystemVersion{VelaVersion: version.VelaVersion, GitVersion: version.GitRevision}}, nil
}
func (u systemInfoUsecaseImpl) DeleteSystemInfo(ctx context.Context) error {
info, err := u.GetSystemInfo(ctx)
if err != nil {
return err
}
err = u.ds.Delete(ctx, info)
if err != nil {
return err
}
return nil
}
+62 -19
View File
@@ -19,6 +19,7 @@ package usecase
import (
"context"
"errors"
"fmt"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -38,7 +39,9 @@ type TargetUsecase interface {
DeleteTarget(ctx context.Context, TargetName string) error
CreateTarget(ctx context.Context, req apisv1.CreateTargetRequest) (*apisv1.DetailTargetResponse, error)
UpdateTarget(ctx context.Context, Target *model.Target, req apisv1.UpdateTargetRequest) (*apisv1.DetailTargetResponse, error)
ListTargets(ctx context.Context, page, pageSize int) (*apisv1.ListTargetResponse, error)
ListTargets(ctx context.Context, page, pageSize int, projectName string) (*apisv1.ListTargetResponse, error)
ListTargetCount(ctx context.Context, projectName string) (int64, error)
Init(ctx context.Context) error
}
type targetUsecaseImpl struct {
@@ -57,20 +60,42 @@ func NewTargetUsecase(ds datastore.DataStore) TargetUsecase {
ds: ds,
}
}
func (dt *targetUsecaseImpl) ListTargets(ctx context.Context, page, pageSize int) (*apisv1.ListTargetResponse, error) {
Targets, err := listTarget(ctx, dt.ds, &datastore.ListOptions{Page: page, PageSize: pageSize, SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}}})
func (dt *targetUsecaseImpl) Init(ctx context.Context) error {
targets, err := dt.ds.List(ctx, &model.Target{}, &datastore.ListOptions{FilterOptions: datastore.FilterOptions{
IsNotExist: []datastore.IsNotExistQueryOption{
{
Key: "project",
},
},
}})
if err != nil {
return fmt.Errorf("list target failure %w", err)
}
for _, target := range targets {
t := target.(*model.Target)
t.Project = model.DefaultInitName
if err := dt.ds.Put(ctx, t); err != nil {
return err
}
}
return nil
}
func (dt *targetUsecaseImpl) ListTargets(ctx context.Context, page, pageSize int, projectName string) (*apisv1.ListTargetResponse, error) {
targets, err := listTarget(ctx, dt.ds, projectName, &datastore.ListOptions{
Page: page,
PageSize: pageSize,
SortBy: []datastore.SortOption{{Key: "createTime", Order: datastore.SortOrderDescending}},
})
if err != nil {
return nil, err
}
resp := &apisv1.ListTargetResponse{
Targets: []apisv1.TargetBase{},
}
for _, raw := range Targets {
for _, raw := range targets {
resp.Targets = append(resp.Targets, *(dt.convertFromTargetModel(ctx, raw)))
}
count, err := dt.ds.Count(ctx, &model.Target{}, nil)
count, err := dt.ds.Count(ctx, &model.Target{Project: projectName}, nil)
if err != nil {
return nil, err
}
@@ -79,9 +104,13 @@ func (dt *targetUsecaseImpl) ListTargets(ctx context.Context, page, pageSize int
return resp, nil
}
func (dt *targetUsecaseImpl) ListTargetCount(ctx context.Context, projectName string) (int64, error) {
return dt.ds.Count(ctx, &model.Target{Project: projectName}, nil)
}
// DeleteTarget delete application Target
func (dt *targetUsecaseImpl) DeleteTarget(ctx context.Context, targetName string) error {
Target := &model.Target{
target := &model.Target{
Name: targetName,
}
ddt, err := dt.GetTarget(ctx, targetName)
@@ -94,7 +123,7 @@ func (dt *targetUsecaseImpl) DeleteTarget(ctx context.Context, targetName string
if err = deleteTargetNamespace(ctx, dt.k8sClient, ddt.Cluster.ClusterName, ddt.Cluster.Namespace, targetName); err != nil {
return err
}
if err = dt.ds.Delete(ctx, Target); err != nil {
if err = dt.ds.Delete(ctx, target); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
return bcode.ErrTargetNotExist
}
@@ -106,26 +135,32 @@ func (dt *targetUsecaseImpl) DeleteTarget(ctx context.Context, targetName string
// CreateTarget will create a delivery target binding with a cluster and namespace, by default, it will use local cluster and namespace align with targetName
// TODO(@wonderflow): we should support empty target in the future which only delivery cloud resources
func (dt *targetUsecaseImpl) CreateTarget(ctx context.Context, req apisv1.CreateTargetRequest) (*apisv1.DetailTargetResponse, error) {
Target := convertCreateReqToTargetModel(req)
var project = model.Project{
Name: req.Project,
}
if err := dt.ds.Get(ctx, &project); err != nil {
return nil, bcode.ErrProjectIsNotExist
}
target := convertCreateReqToTargetModel(req)
if req.Cluster == nil {
req.Cluster = &apisv1.ClusterTarget{ClusterName: multicluster.ClusterLocalName, Namespace: req.Name}
}
if err := createTargetNamespace(ctx, dt.k8sClient, req.Cluster.ClusterName, req.Cluster.Namespace, req.Name); err != nil {
return nil, err
}
err := createTarget(ctx, dt.ds, &Target)
err := createTarget(ctx, dt.ds, &target)
if err != nil {
return nil, err
}
return dt.DetailTarget(ctx, &Target)
return dt.DetailTarget(ctx, &target)
}
func (dt *targetUsecaseImpl) UpdateTarget(ctx context.Context, target *model.Target, req apisv1.UpdateTargetRequest) (*apisv1.DetailTargetResponse, error) {
TargetModel := convertUpdateReqToTargetModel(target, req)
if err := dt.ds.Put(ctx, TargetModel); err != nil {
targetModel := convertUpdateReqToTargetModel(target, req)
if err := dt.ds.Put(ctx, targetModel); err != nil {
return nil, err
}
return dt.DetailTarget(ctx, TargetModel)
return dt.DetailTarget(ctx, targetModel)
}
// DetailTarget detail Target
@@ -154,14 +189,15 @@ func convertUpdateReqToTargetModel(target *model.Target, req apisv1.UpdateTarget
}
func convertCreateReqToTargetModel(req apisv1.CreateTargetRequest) model.Target {
Target := model.Target{
target := model.Target{
Name: req.Name,
Alias: req.Alias,
Description: req.Description,
Cluster: (*model.ClusterTarget)(req.Cluster),
Variable: req.Variable,
Project: req.Project,
}
return Target
return target
}
func (dt *targetUsecaseImpl) convertFromTargetModel(ctx context.Context, target *model.Target) *apisv1.TargetBase {
@@ -177,7 +213,15 @@ func (dt *targetUsecaseImpl) convertFromTargetModel(ctx context.Context, target
UpdateTime: target.UpdateTime,
AppNum: appNum,
}
if target.Project != "" {
var project = model.Project{
Name: target.Project,
}
if err := dt.ds.Get(ctx, &project); err != nil {
log.Logger.Errorf("get project failure %s", err.Error())
}
targetBase.Project = apisv1.NameAlias{Name: project.Name, Alias: project.Alias}
}
if targetBase.Cluster != nil && targetBase.Cluster.ClusterName != "" {
cluster, err := _getClusterFromDataStore(ctx, dt.ds, target.Cluster.ClusterName)
if err != nil {
@@ -187,6 +231,5 @@ func (dt *targetUsecaseImpl) convertFromTargetModel(ctx context.Context, target
targetBase.ClusterAlias = cluster.Alias
}
}
return targetBase
}
+6 -4
View File
@@ -85,13 +85,15 @@ func createTarget(ctx context.Context, ds datastore.DataStore, tg *model.Target)
return nil
}
func listTarget(ctx context.Context, ds datastore.DataStore, dsOption *datastore.ListOptions) ([]*model.Target, error) {
func listTarget(ctx context.Context, ds datastore.DataStore, project string, dsOption *datastore.ListOptions) ([]*model.Target, error) {
if dsOption == nil {
dsOption = &datastore.ListOptions{}
}
Target := model.Target{}
Targets, err := ds.List(ctx, &Target, dsOption)
target := model.Target{}
if project != "" {
target.Project = project
}
Targets, err := ds.List(ctx, &target, dsOption)
if err != nil {
log.Logger.Errorf("list target err %v", err)
return nil, err
+4 -2
View File
@@ -38,7 +38,8 @@ var _ = Describe("Test target usecase functions", func() {
ds, err := NewDatastore(datastore.Config{Type: "kubeapi", Database: "target-test-kubevela"})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
projectUsecase = &projectUsecaseImpl{ds: ds, k8sClient: k8sClient}
rbacUsecase := &rbacUsecaseImpl{ds: ds}
projectUsecase = &projectUsecaseImpl{ds: ds, k8sClient: k8sClient, rbacUsecase: rbacUsecase}
targetUsecase = &targetUsecaseImpl{ds: ds, k8sClient: k8sClient}
})
It("Test CreateTarget function", func() {
@@ -49,6 +50,7 @@ var _ = Describe("Test target usecase functions", func() {
Name: "test--target",
Alias: "test-alias",
Description: "this is a Target",
Project: testProject,
Cluster: &apisv1.ClusterTarget{ClusterName: "cluster-dev", Namespace: "dev"},
Variable: map[string]interface{}{"terraform-provider": "provider", "region": "us-1"},
}
@@ -65,7 +67,7 @@ var _ = Describe("Test target usecase functions", func() {
Expect(cmp.Diff(Target.Name, "test--target")).Should(BeEmpty())
By("Test ListTargets function")
resp, err := targetUsecase.ListTargets(context.TODO(), 1, 1)
resp, err := targetUsecase.ListTargets(context.TODO(), 1, 1, "")
Expect(err).Should(BeNil())
Expect(resp.Targets[0].ClusterAlias).Should(Equal("dev-alias"))
+164 -19
View File
@@ -18,17 +18,24 @@ package usecase
import (
"context"
"errors"
"golang.org/x/crypto/bcrypt"
"helm.sh/helm/v3/pkg/time"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8stypes "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
utils2 "github.com/oam-dev/kubevela/pkg/utils"
)
// UserUsecase User manage api
@@ -41,18 +48,21 @@ type UserUsecase interface {
ListUsers(ctx context.Context, page, pageSize int, listOptions apisv1.ListUserOptions) (*apisv1.ListUserResponse, error)
DisableUser(ctx context.Context, user *model.User) error
EnableUser(ctx context.Context, user *model.User) error
updateUserLoginTime(ctx context.Context, user *model.User) error
DetailLoginUserInfo(ctx context.Context) (*apisv1.LoginUserInfoResponse, error)
UpdateUserLoginTime(ctx context.Context, user *model.User) error
Init(ctx context.Context) error
}
type userUsecaseImpl struct {
ds datastore.DataStore
k8sClient client.Client
projectUsecase ProjectUsecase
rbacUsecase RBACUsecase
sysUsecase SystemInfoUsecase
}
// NewUserUsecase new User usecase
func NewUserUsecase(ds datastore.DataStore, projectUsecase ProjectUsecase, sysUsecase SystemInfoUsecase) UserUsecase {
func NewUserUsecase(ds datastore.DataStore, projectUsecase ProjectUsecase, sysUsecase SystemInfoUsecase, rbacUsecase RBACUsecase) UserUsecase {
k8sClient, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("get k8sClient failure: %s", err.Error())
@@ -62,9 +72,60 @@ func NewUserUsecase(ds datastore.DataStore, projectUsecase ProjectUsecase, sysUs
ds: ds,
projectUsecase: projectUsecase,
sysUsecase: sysUsecase,
rbacUsecase: rbacUsecase,
}
}
func (u *userUsecaseImpl) Init(ctx context.Context) error {
admin := model.DefaultAdminUserName
if err := u.ds.Get(ctx, &model.User{
Name: admin,
}); err != nil {
if errors.Is(err, datastore.ErrRecordNotExist) {
pwd := utils2.RandomString(8)
encrypted, err := GeneratePasswordHash(pwd)
if err != nil {
return err
}
if err := u.ds.Add(ctx, &model.User{
Name: admin,
Alias: "Administrator",
Password: encrypted,
UserRoles: []string{"admin"},
}); err != nil {
return err
}
// print default password of admin user in log
log.Logger.Infof("init admin user, password is %s", pwd)
secret := &corev1.Secret{}
if err := u.k8sClient.Get(ctx, k8stypes.NamespacedName{
Name: admin,
Namespace: types.DefaultKubeVelaNS,
}, secret); err != nil {
if apierrors.IsNotFound(err) {
if err := u.k8sClient.Create(ctx, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: admin,
Namespace: types.DefaultKubeVelaNS,
},
StringData: map[string]string{
admin: pwd,
},
}); err != nil {
return err
}
} else {
return err
}
}
} else {
return err
}
}
log.Logger.Info("admin user is exist")
return nil
}
// GetUser get user
func (u *userUsecaseImpl) GetUser(ctx context.Context, username string) (*model.User, error) {
user := &model.User{
@@ -78,7 +139,11 @@ func (u *userUsecaseImpl) GetUser(ctx context.Context, username string) (*model.
// DetailUser return user detail
func (u *userUsecaseImpl) DetailUser(ctx context.Context, user *model.User) (*apisv1.DetailUserResponse, error) {
detailUser := convertUserModel(user)
roles, err := u.rbacUsecase.ListRole(ctx, "", 0, 0)
if err != nil {
log.Logger.Warnf("list platform roles failure %s", err.Error())
}
detailUser := convertUserModel(user, roles)
pUser := &model.ProjectUser{
Username: user.Name,
}
@@ -91,16 +156,12 @@ func (u *userUsecaseImpl) DetailUser(ctx context.Context, user *model.User) (*ap
for _, v := range projectUsers {
pu, ok := v.(*model.ProjectUser)
if ok {
project, err := u.projectUsecase.GetProject(ctx, pu.ProjectName)
project, err := u.projectUsecase.DetailProject(ctx, pu.ProjectName)
if err != nil {
log.Logger.Errorf("failed to delete project(%s) info: %s", pu.ProjectName, err.Error())
continue
}
detailUser.Projects = append(detailUser.Projects, apisv1.ProjectUserBase{
Name: pu.ProjectName,
Alias: project.Alias,
UserRoles: pu.UserRoles,
})
detailUser.Projects = append(detailUser.Projects, project)
}
}
return detailUser, nil
@@ -142,12 +203,14 @@ func (u *userUsecaseImpl) CreateUser(ctx context.Context, req apisv1.CreateUserR
if err != nil {
return nil, err
}
// TODO: validate the roles, they must be platform roles
user := &model.User{
Name: req.Name,
Alias: req.Alias,
Email: req.Email,
Password: hash,
Disabled: false,
Name: req.Name,
Alias: req.Alias,
Email: req.Email,
UserRoles: req.Roles,
Password: hash,
Disabled: false,
}
if err := u.ds.Add(ctx, user); err != nil {
return nil, err
@@ -180,6 +243,10 @@ func (u *userUsecaseImpl) UpdateUser(ctx context.Context, user *model.User, req
}
user.Email = req.Email
}
// TODO: validate the roles, they must be platform roles
if req.Roles != nil {
user.UserRoles = *req.Roles
}
if err := u.ds.Put(ctx, user); err != nil {
return nil, err
}
@@ -211,10 +278,14 @@ func (u *userUsecaseImpl) ListUsers(ctx context.Context, page, pageSize int, lis
if err != nil {
return nil, err
}
roles, err := u.rbacUsecase.ListRole(ctx, "", 0, 0)
if err != nil {
log.Logger.Warnf("list platform roles failure %s", err.Error())
}
for _, v := range users {
user, ok := v.(*model.User)
if ok {
userList = append(userList, convertUserModel(user))
userList = append(userList, convertUserModel(user, roles))
}
}
count, err := u.ds.Count(ctx, user, &fo)
@@ -246,16 +317,90 @@ func (u *userUsecaseImpl) EnableUser(ctx context.Context, user *model.User) erro
return u.ds.Put(ctx, user)
}
// updateUserLoginTime update user login time
func (u *userUsecaseImpl) updateUserLoginTime(ctx context.Context, user *model.User) error {
// UpdateUserLoginTime update user login time
func (u *userUsecaseImpl) UpdateUserLoginTime(ctx context.Context, user *model.User) error {
user.LastLoginTime = time.Now().Time
return u.ds.Put(ctx, user)
}
func convertUserModel(user *model.User) *apisv1.DetailUserResponse {
// DetailLoginUserInfo get projects and permission policies of login user
func (u *userUsecaseImpl) DetailLoginUserInfo(ctx context.Context) (*apisv1.LoginUserInfoResponse, error) {
userName, ok := ctx.Value(&apisv1.CtxKeyUser).(string)
if !ok {
return nil, bcode.ErrUnauthorized
}
user, err := u.GetUser(ctx, userName)
if !ok {
log.Logger.Errorf("get login user model failure %s", err.Error())
return nil, bcode.ErrUnauthorized
}
projects, err := u.projectUsecase.ListUserProjects(ctx, userName)
if err != nil {
return nil, err
}
var projectPermissions = make(map[string][]apisv1.PermissionBase)
for _, project := range projects {
perms, err := u.rbacUsecase.GetUserPermissions(ctx, user, project.Name, false)
if err != nil {
log.Logger.Errorf("list user %s perm policies from project %s failure %s", user.Name, project.Name, err.Error())
continue
}
projectPermissions[project.Name] = func() (list []apisv1.PermissionBase) {
for _, perm := range perms {
list = append(list, apisv1.PermissionBase{
Name: perm.Name,
Alias: perm.Alias,
Resources: perm.Resources,
Actions: perm.Actions,
Effect: perm.Effect,
CreateTime: perm.CreateTime,
UpdateTime: perm.UpdateTime,
})
}
return
}()
}
perms, err := u.rbacUsecase.GetUserPermissions(ctx, user, "", true)
if err != nil {
log.Logger.Errorf("list user %s platform perm policies failure %s", user.Name, err.Error())
}
var platformPermissions []apisv1.PermissionBase
for _, perm := range perms {
platformPermissions = append(platformPermissions, apisv1.PermissionBase{
Name: perm.Name,
Alias: perm.Alias,
Resources: perm.Resources,
Actions: perm.Actions,
Effect: perm.Effect,
CreateTime: perm.CreateTime,
UpdateTime: perm.UpdateTime,
})
}
return &apisv1.LoginUserInfoResponse{
UserBase: *convertUserBase(user),
Projects: projects,
ProjectPermissions: projectPermissions,
PlatformPermissions: platformPermissions,
}, nil
}
func convertUserModel(user *model.User, roles *apisv1.ListRolesResponse) *apisv1.DetailUserResponse {
var nameAlias = make(map[string]string)
if roles != nil {
for _, role := range roles.Roles {
nameAlias[role.Name] = role.Alias
}
}
return &apisv1.DetailUserResponse{
UserBase: *convertUserBase(user),
Projects: make([]apisv1.ProjectUserBase, 0),
Roles: func() (list []apisv1.NameAlias) {
for _, r := range user.UserRoles {
list = append(list, apisv1.NameAlias{Name: r, Alias: nameAlias[r]})
}
return
}(),
Projects: make([]*apisv1.ProjectBase, 0),
}
}
+4 -14
View File
@@ -46,9 +46,10 @@ var _ = Describe("Test authentication usecase functions", func() {
ds, err = NewDatastore(datastore.Config{Type: "kubeapi", Database: db})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
projectUsecase := &projectUsecaseImpl{k8sClient: k8sClient, ds: ds}
rbacUsecase := &rbacUsecaseImpl{ds: ds}
projectUsecase := &projectUsecaseImpl{k8sClient: k8sClient, ds: ds, rbacUsecase: rbacUsecase}
sysUsecase := &systemInfoUsecaseImpl{ds: ds}
userUsecase = &userUsecaseImpl{ds: ds, projectUsecase: projectUsecase, sysUsecase: sysUsecase}
userUsecase = &userUsecaseImpl{ds: ds, projectUsecase: projectUsecase, sysUsecase: sysUsecase, rbacUsecase: rbacUsecase}
})
AfterEach(func() {
err := k8sClient.Delete(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: db}})
@@ -111,18 +112,7 @@ var _ = Describe("Test authentication usecase functions", func() {
Expect(user.Name).Should(Equal("name"))
Expect(user.Alias).Should(Equal("alias"))
Expect(user.Email).Should(Equal("email@example.com"))
Expect(user.Projects).Should(Equal([]apisv1.ProjectUserBase{
{
Name: "project-1",
Alias: "project-alias-1",
UserRoles: []string{"user-role-1"},
},
{
Name: "project-0",
Alias: "project-alias-0",
UserRoles: []string{"user-role-0"},
},
}))
Expect(len(user.Projects)).Should(Equal(2))
})
It("Test list users", func() {
+5 -4
View File
@@ -49,12 +49,13 @@ var _ = Describe("Test application usecase function", func() {
ds, err := NewDatastore(datastore.Config{Type: "kubeapi", Database: "app-test-kubevela"})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
envUsecase = &envUsecaseImpl{ds: ds, kubeClient: k8sClient}
workflowUsecase = &workflowUsecaseImpl{ds: ds, envUsecase: envUsecase}
definitionUsecase = &definitionUsecaseImpl{kubeClient: k8sClient}
envBindingUsecase = &envBindingUsecaseImpl{ds: ds, envUsecase: envUsecase, workflowUsecase: workflowUsecase, kubeClient: k8sClient, definitionUsecase: definitionUsecase}
targetUsecase = &targetUsecaseImpl{ds: ds, k8sClient: k8sClient}
projectUsecase = &projectUsecaseImpl{ds: ds, k8sClient: k8sClient}
rbacUsecase := &rbacUsecaseImpl{ds: ds}
projectUsecase = &projectUsecaseImpl{ds: ds, k8sClient: k8sClient, rbacUsecase: rbacUsecase}
envUsecase = &envUsecaseImpl{ds: ds, kubeClient: k8sClient, projectUsecase: projectUsecase}
appUsecase = &applicationUsecaseImpl{
ds: ds,
workflowUsecase: workflowUsecase,
@@ -73,10 +74,10 @@ var _ = Describe("Test application usecase function", func() {
})
It("Test HandleApplicationWebhook function", func() {
_, err := targetUsecase.CreateTarget(context.TODO(), apisv1.CreateTargetRequest{Name: "dev-target-webhook"})
_, err := projectUsecase.CreateProject(context.TODO(), apisv1.CreateProjectRequest{Name: "project-webhook"})
Expect(err).Should(BeNil())
_, err = projectUsecase.CreateProject(context.TODO(), apisv1.CreateProjectRequest{Name: "project-webhook"})
_, err = targetUsecase.CreateTarget(context.TODO(), apisv1.CreateTargetRequest{Name: "dev-target-webhook", Project: "project-webhook"})
Expect(err).Should(BeNil())
_, err = envUsecase.CreateEnv(context.TODO(), apisv1.CreateEnvRequest{Name: "webhook-dev", Namespace: "webhook-dev", Targets: []string{"dev-target-webhook"}, Project: "project-webhook"})
+3 -2
View File
@@ -57,8 +57,9 @@ var _ = Describe("Test workflow usecase functions", func() {
ds, err = NewDatastore(datastore.Config{Type: "kubeapi", Database: "workflow-test-" + strconv.FormatInt(time.Now().UnixNano(), 10)})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
projectUsecase = &projectUsecaseImpl{ds: ds}
envUsecase = &envUsecaseImpl{ds: ds, kubeClient: k8sClient}
rbacUsecase := &rbacUsecaseImpl{ds: ds}
projectUsecase = &projectUsecaseImpl{ds: ds, rbacUsecase: rbacUsecase}
envUsecase = &envUsecaseImpl{ds: ds, kubeClient: k8sClient, projectUsecase: projectUsecase}
workflowUsecase = &workflowUsecaseImpl{
ds: ds,
kubeClient: k8sClient,
@@ -27,3 +27,21 @@ var ErrProjectNamespaceFail = NewBcode(400, 30003, "project bind namespace failu
// ErrProjectNamespaceIsExist the namespace belongs to the other project
var ErrProjectNamespaceIsExist = NewBcode(400, 30004, "the namespace belongs to the other project")
// ErrProjectDenyDeleteByApplication the project can't be deleted as there are applications inside
var ErrProjectDenyDeleteByApplication = NewBcode(400, 30005, "the project can't be deleted as there are applications inside")
// ErrProjectDenyDeleteByEnvironment the project can't be deleted because there are environments inside
var ErrProjectDenyDeleteByEnvironment = NewBcode(400, 30006, "the project can't be deleted before you clean up all the environments inside")
// ErrProjectDenyDeleteByTarget the project can't be deleted as there are targets inside
var ErrProjectDenyDeleteByTarget = NewBcode(400, 30007, "the project can't be deleted before you clean up all these targets inside")
// ErrProjectRoleCheckFailure means the specified role does't belong to this project or not exist
var ErrProjectRoleCheckFailure = NewBcode(400, 30008, "the specified role does't belong to this project or not exist")
// ErrProjectUserExist means the user is already exist in this project
var ErrProjectUserExist = NewBcode(400, 30009, "the user is already exist in this project")
// ErrProjectOwnerIsNotExist means the project owner name is invalid
var ErrProjectOwnerIsNotExist = NewBcode(400, 30010, "the project owner name is invalid")
@@ -27,4 +27,6 @@ var (
ErrUserCannotModified = NewBcode(400, 14004, "the user cannot be modified in dex login mode")
// ErrUserInvalidPassword is the error of user invalid password
ErrUserInvalidPassword = NewBcode(400, 14005, "the password is invalid")
// ErrDexConfigNotFound means the dex config is not configured
ErrDexConfigNotFound = NewBcode(200, 14006, "the dex config is not found")
)
@@ -0,0 +1,28 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package bcode
var (
// ErrRolePermissionCheckFailure means the perm policy is invalid where create or update role
ErrRolePermissionCheckFailure = NewBcode(400, 15001, "the permissions are invalid")
// ErrRoleIsExist means the role is exist
ErrRoleIsExist = NewBcode(400, 15002, "the role name is exist")
// ErrRoleIsNotExist means the role is not exist
ErrRoleIsNotExist = NewBcode(400, 15003, "the role is not exist")
// ErrPermissionNotExist means the permission is not exist
ErrPermissionNotExist = NewBcode(404, 15004, "the permission is not exist")
)
+6
View File
@@ -35,6 +35,12 @@ import (
// ErrServer an unexpected mistake.
var ErrServer = NewBcode(500, 500, "The service has lapsed.")
// ErrForbidden check user perms failure
var ErrForbidden = NewBcode(403, 403, "403 Forbidden")
// ErrUnauthorized check user auth failure
var ErrUnauthorized = NewBcode(401, 401, "401 Unauthorized")
// Bcode business error code
type Bcode struct {
HTTPCode int32 `json:"-"`
+31 -20
View File
@@ -29,21 +29,24 @@ import (
)
// NewAddonWebService returns addon web service
func NewAddonWebService(u usecase.AddonHandler) WebService {
func NewAddonWebService(u usecase.AddonHandler, rbacUsecase usecase.RBACUsecase) WebService {
return &addonWebService{
handler: u,
handler: u,
rbacUsecase: rbacUsecase,
}
}
// NewEnabledAddonWebService returns enabled addon web service
func NewEnabledAddonWebService(u usecase.AddonHandler) WebService {
func NewEnabledAddonWebService(u usecase.AddonHandler, rbacUsecase usecase.RBACUsecase) WebService {
return &enabledAddonWebService{
addonUsecase: u,
rbacUsecase: rbacUsecase,
}
}
type addonWebService struct {
handler usecase.AddonHandler
rbacUsecase usecase.RBACUsecase
handler usecase.AddonHandler
}
func (s *addonWebService) GetWebService() *restful.WebService {
@@ -59,6 +62,7 @@ func (s *addonWebService) GetWebService() *restful.WebService {
ws.Route(ws.GET("/").To(s.listAddons).
Doc("list all addons").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUsecase.CheckPerm("addon", "list")).
Param(ws.QueryParameter("registry", "filter addons from given registry").DataType("string")).
Param(ws.QueryParameter("query", "Fuzzy search based on name and description.").DataType("string")).
Returns(200, "OK", apis.ListAddonResponse{}).
@@ -66,53 +70,58 @@ func (s *addonWebService) GetWebService() *restful.WebService {
Writes(apis.ListAddonResponse{}))
// GET
ws.Route(ws.GET("/{name}").To(s.detailAddon).
ws.Route(ws.GET("/{addonName}").To(s.detailAddon).
Doc("show details of an addon").
Metadata(restfulspec.KeyOpenAPITags, tags).
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUsecase.CheckPerm("addon", "detail")).
Returns(200, "OK", apis.DetailAddonResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Param(ws.PathParameter("name", "addon name to query detail").DataType("string").Required(true)).
Param(ws.PathParameter("addonName", "addon name to query detail").DataType("string").Required(true)).
Param(ws.QueryParameter("registry", "filter addons from given registry").DataType("string")).
Writes(apis.DetailAddonResponse{}))
// GET status
ws.Route(ws.GET("/{name}/status").To(s.statusAddon).
ws.Route(ws.GET("/{addonName}/status").To(s.statusAddon).
Doc("show status of an addon").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUsecase.CheckPerm("addon", "detail")).
Returns(200, "OK", apis.AddonStatusResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Param(ws.PathParameter("name", "addon name to query status").DataType("string").Required(true)).
Param(ws.PathParameter("addonName", "addon name to query status").DataType("string").Required(true)).
Writes(apis.AddonStatusResponse{}))
// enable addon
ws.Route(ws.POST("/{name}/enable").To(s.enableAddon).
ws.Route(ws.POST("/{addonName}/enable").To(s.enableAddon).
Doc("enable an addon").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.EnableAddonRequest{}).
Filter(s.rbacUsecase.CheckPerm("addon", "enable")).
Returns(200, "OK", apis.AddonStatusResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Param(ws.PathParameter("name", "addon name to enable").DataType("string").Required(true)).
Param(ws.PathParameter("addonName", "addon name to enable").DataType("string").Required(true)).
Writes(apis.AddonStatusResponse{}))
// disable addon
ws.Route(ws.POST("/{name}/disable").To(s.disableAddon).
ws.Route(ws.POST("/{addonName}/disable").To(s.disableAddon).
Doc("disable an addon").
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "OK", apis.AddonStatusResponse{}).
Filter(s.rbacUsecase.CheckPerm("addon", "disable")).
Returns(400, "Bad Request", bcode.Bcode{}).
Param(ws.PathParameter("name", "addon name to enable").DataType("string").Required(true)).
Param(ws.PathParameter("addonName", "addon name to enable").DataType("string").Required(true)).
Param(ws.QueryParameter("force", "force disable an addon").DataType("boolean").Required(false)).
Writes(apis.AddonStatusResponse{}))
// update addon
ws.Route(ws.PUT("/{name}/update").To(s.updateAddon).
ws.Route(ws.PUT("/{addonName}/update").To(s.updateAddon).
Doc("update an addon").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.EnableAddonRequest{}).
Returns(200, "OK", apis.AddonStatusResponse{}).
Filter(s.rbacUsecase.CheckPerm("addon", "update")).
Returns(400, "Bad Request", bcode.Bcode{}).
Param(ws.PathParameter("name", "addon name to update").DataType("string").Required(true)).
Param(ws.PathParameter("addonName", "addon name to update").DataType("string").Required(true)).
Writes(apis.AddonStatusResponse{}))
ws.Filter(authCheckFilter)
@@ -144,7 +153,7 @@ func (s *addonWebService) listAddons(req *restful.Request, res *restful.Response
}
func (s *addonWebService) detailAddon(req *restful.Request, res *restful.Response) {
name := req.PathParameter("name")
name := req.PathParameter("addonName")
addon, err := s.handler.GetAddon(req.Request.Context(), name, req.QueryParameter("registry"))
if err != nil {
bcode.ReturnError(req, res, err)
@@ -178,8 +187,7 @@ func (s *addonWebService) enableAddon(req *restful.Request, res *restful.Respons
createReq.Args[types.ClustersArg] = createReq.Clusters
}
name := req.PathParameter("name")
name := req.PathParameter("addonName")
err = s.handler.EnableAddon(req.Request.Context(), name, createReq)
if err != nil {
bcode.ReturnError(req, res, err)
@@ -190,7 +198,7 @@ func (s *addonWebService) enableAddon(req *restful.Request, res *restful.Respons
}
func (s *addonWebService) disableAddon(req *restful.Request, res *restful.Response) {
name := req.PathParameter("name")
name := req.PathParameter("addonName")
forceParam := req.QueryParameter("force")
force, _ := strconv.ParseBool(forceParam)
err := s.handler.DisableAddon(req.Request.Context(), name, force)
@@ -202,7 +210,7 @@ func (s *addonWebService) disableAddon(req *restful.Request, res *restful.Respon
}
func (s *addonWebService) statusAddon(req *restful.Request, res *restful.Response) {
name := req.PathParameter("name")
name := req.PathParameter("addonName")
status, err := s.handler.StatusAddon(req.Request.Context(), name)
if err != nil {
bcode.ReturnError(req, res, err)
@@ -235,7 +243,7 @@ func (s *addonWebService) updateAddon(req *restful.Request, res *restful.Respons
createReq.Args[types.ClustersArg] = createReq.Clusters
}
name := req.PathParameter("name")
name := req.PathParameter("addonName")
err = s.handler.UpdateAddon(req.Request.Context(), name, createReq)
if err != nil {
bcode.ReturnError(req, res, err)
@@ -247,6 +255,7 @@ func (s *addonWebService) updateAddon(req *restful.Request, res *restful.Respons
type enabledAddonWebService struct {
addonUsecase usecase.AddonHandler
rbacUsecase usecase.RBACUsecase
}
func (s *enabledAddonWebService) GetWebService() *restful.WebService {
@@ -262,12 +271,14 @@ func (s *enabledAddonWebService) GetWebService() *restful.WebService {
ws.Route(ws.GET("/").To(s.list).
Doc("list all addons").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUsecase.CheckPerm("addon", "list")).
Param(ws.QueryParameter("registry", "filter addons from given registry").DataType("string")).
Param(ws.QueryParameter("query", "Fuzzy search based on name and description.").DataType("string")).
Returns(200, "OK", apis.ListAddonResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ListAddonResponse{}))
ws.Filter(authCheckFilter)
return ws
}
@@ -26,14 +26,16 @@ import (
)
// NewAddonRegistryWebService returns addon registry web service
func NewAddonRegistryWebService(u usecase.AddonHandler) WebService {
func NewAddonRegistryWebService(u usecase.AddonHandler, rbacUsecase usecase.RBACUsecase) WebService {
return &addonRegistryWebService{
addonUsecase: u,
rbacUsecase: rbacUsecase,
}
}
type addonRegistryWebService struct {
addonUsecase usecase.AddonHandler
rbacUsecase usecase.RBACUsecase
}
func (s *addonRegistryWebService) GetWebService() *restful.WebService {
@@ -50,6 +52,7 @@ func (s *addonRegistryWebService) GetWebService() *restful.WebService {
Doc("create an addon registry").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.CreateAddonRegistryRequest{}).
Filter(s.rbacUsecase.CheckPerm("addonRegistry", "create")).
Returns(200, "OK", apis.AddonRegistry{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.AddonRegistry{}))
@@ -57,24 +60,27 @@ func (s *addonRegistryWebService) GetWebService() *restful.WebService {
ws.Route(ws.GET("/").To(s.listAddonRegistry).
Doc("list all addon registry").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(s.rbacUsecase.CheckPerm("addonRegistry", "list")).
Returns(200, "OK", apis.ListAddonRegistryResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ListAddonRegistryResponse{}))
// Delete
ws.Route(ws.DELETE("/{name}").To(s.deleteAddonRegistry).
ws.Route(ws.DELETE("/{addonRegName}").To(s.deleteAddonRegistry).
Doc("delete an addon registry").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("name", "identifier of the addon registry").DataType("string")).
Param(ws.PathParameter("addonRegName", "identifier of the addon registry").DataType("string")).
Returns(200, "OK", apis.AddonRegistry{}).
Filter(s.rbacUsecase.CheckPerm("addonRegistry", "delete")).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.AddonRegistry{}))
ws.Route(ws.PUT("/{name}").To(s.updateAddonRegistry).
ws.Route(ws.PUT("/{addonRegName}").To(s.updateAddonRegistry).
Doc("update an addon registry").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.UpdateAddonRegistryRequest{}).
Param(ws.PathParameter("name", "identifier of the addon registry").DataType("string")).
Filter(s.rbacUsecase.CheckPerm("addonRegistry", "update")).
Param(ws.PathParameter("addonRegName", "identifier of the addon registry").DataType("string")).
Returns(200, "OK", apis.AddonRegistry{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.AddonRegistry{}))
@@ -110,7 +116,7 @@ func (s *addonRegistryWebService) createAddonRegistry(req *restful.Request, res
}
func (s *addonRegistryWebService) deleteAddonRegistry(req *restful.Request, res *restful.Response) {
r, err := s.addonUsecase.GetAddonRegistry(req.Request.Context(), req.PathParameter("name"))
r, err := s.addonUsecase.GetAddonRegistry(req.Request.Context(), req.PathParameter("addonRegName"))
if err != nil {
bcode.ReturnError(req, res, err)
return
@@ -151,7 +157,7 @@ func (s *addonRegistryWebService) updateAddonRegistry(req *restful.Request, res
return
}
// Call the usecase layer code
meta, err := s.addonUsecase.UpdateAddonRegistry(req.Request.Context(), req.PathParameter("name"), updateReq)
meta, err := s.addonUsecase.UpdateAddonRegistry(req.Request.Context(), req.PathParameter("addonRegName"), updateReq)
if err != nil {
bcode.ReturnError(req, res, err)
return
+152 -96
View File
@@ -33,17 +33,19 @@ import (
type applicationWebService struct {
workflowWebService
rbacUsecase usecase.RBACUsecase
applicationUsecase usecase.ApplicationUsecase
envBindingUsecase usecase.EnvBindingUsecase
}
// NewApplicationWebService new application manage webservice
func NewApplicationWebService(applicationUsecase usecase.ApplicationUsecase, envBindingUsecase usecase.EnvBindingUsecase, workflowUsecase usecase.WorkflowUsecase) WebService {
func NewApplicationWebService(applicationUsecase usecase.ApplicationUsecase, envBindingUsecase usecase.EnvBindingUsecase, workflowUsecase usecase.WorkflowUsecase, rbacUsecase usecase.RBACUsecase) WebService {
return &applicationWebService{
workflowWebService: workflowWebService{
workflowUsecase: workflowUsecase,
applicationUsecase: applicationUsecase,
},
rbacUsecase: rbacUsecase,
applicationUsecase: applicationUsecase,
envBindingUsecase: envBindingUsecase,
}
@@ -62,8 +64,11 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Doc("list all applications").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.QueryParameter("query", "Fuzzy search based on name or description").DataType("string")).
Param(ws.QueryParameter("namespace", "The namespace of the managed cluster").DataType("string")).
Param(ws.QueryParameter("project", "search base on project name").DataType("string")).
Param(ws.QueryParameter("env", "search base on env name").DataType("string")).
Param(ws.QueryParameter("targetName", "Name of the application delivery target").DataType("string")).
// This api will filter the app by user's permissions
// Filter(c.rbacUsecase.CheckPerm("application", "list")).
Returns(200, "OK", apis.ListApplicationResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ListApplicationResponse{}))
@@ -72,132 +77,146 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Doc("create one application ").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.CreateApplicationRequest{}).
Filter(c.rbacUsecase.CheckPerm("application", "create")).
Returns(200, "OK", apis.ApplicationBase{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ApplicationBase{}))
ws.Route(ws.DELETE("/{name}").To(c.deleteApplication).
ws.Route(ws.DELETE("/{appName}").To(c.deleteApplication).
Doc("delete one application").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("application", "delete")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Returns(200, "OK", apis.EmptyResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.GET("/{name}").To(c.detailApplication).
ws.Route(ws.GET("/{appName}").To(c.detailApplication).
Doc("detail one application ").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("application", "detail")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Returns(200, "OK", apis.DetailApplicationResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.DetailApplicationResponse{}))
ws.Route(ws.PUT("/{name}").To(c.updateApplication).
ws.Route(ws.PUT("/{appName}").To(c.updateApplication).
Doc("update one application ").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("application", "update")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Reads(apis.UpdateApplicationRequest{}).
Returns(200, "OK", apis.ApplicationBase{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ApplicationBase{}))
ws.Route(ws.GET("/{name}/statistics").To(c.applicationStatistics).
ws.Route(ws.GET("/{appName}/statistics").To(c.applicationStatistics).
Doc("detail one application ").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("application", "detail")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Returns(200, "OK", apis.ApplicationStatisticsResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ApplicationStatisticsResponse{}))
ws.Route(ws.POST("/{name}/triggers").To(c.createApplicationTrigger).
ws.Route(ws.POST("/{appName}/triggers").To(c.createApplicationTrigger).
Doc("create one application trigger").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("trigger", "create")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Reads(apis.CreateApplicationTriggerRequest{}).
Returns(200, "OK", apis.ApplicationTriggerBase{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ApplicationTriggerBase{}))
ws.Route(ws.DELETE("/{name}/triggers/{token}").To(c.deleteApplicationTrigger).
ws.Route(ws.DELETE("/{appName}/triggers/{token}").To(c.deleteApplicationTrigger).
Doc("delete one application trigger").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("trigger", "delete")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("token", "identifier of the trigger").DataType("string")).
Returns(200, "OK", apis.EmptyResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes([]*apis.EmptyResponse{}))
ws.Route(ws.GET("/{name}/triggers").To(c.listApplicationTriggers).
ws.Route(ws.GET("/{appName}/triggers").To(c.listApplicationTriggers).
Doc("list application triggers").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("trigger", "list")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Returns(200, "OK", apis.ListApplicationTriggerResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes([]*apis.ApplicationTriggerBase{}))
ws.Route(ws.POST("/{name}/template").To(c.publishApplicationTemplate).
ws.Route(ws.POST("/{appName}/template").To(c.publishApplicationTemplate).
Doc("create one application template").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("applicationTemplate", "create")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Reads(apis.CreateApplicationTemplateRequest{}).
Returns(200, "OK", apis.ApplicationTemplateBase{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ApplicationTemplateBase{}))
ws.Route(ws.POST("/{name}/deploy").To(c.deployApplication).
ws.Route(ws.POST("/{appName}/deploy").To(c.deployApplication).
Doc("deploy or upgrade the application").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("application", "deploy")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Reads(apis.ApplicationDeployRequest{}).
Returns(200, "OK", apis.ApplicationDeployResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ApplicationDeployResponse{}))
ws.Route(ws.GET("/{name}/components").To(c.listApplicationComponents).
ws.Route(ws.GET("/{appName}/components").To(c.listApplicationComponents).
Doc("gets the list of application components").
Filter(c.rbacUsecase.CheckPerm("component", "list")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Param(ws.QueryParameter("envName", "list components that deployed in define env").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "OK", apis.ComponentListResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ComponentListResponse{}))
ws.Route(ws.POST("/{name}/components").To(c.createComponent).
ws.Route(ws.POST("/{appName}/components").To(c.createComponent).
Doc("create component for application ").
Filter(c.rbacUsecase.CheckPerm("component", "create")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.CreateComponentRequest{}).
Returns(200, "OK", apis.ComponentBase{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ComponentBase{}))
ws.Route(ws.GET("/{name}/components/{compName}").To(c.detailComponent).
ws.Route(ws.GET("/{appName}/components/{compName}").To(c.detailComponent).
Doc("detail component for application ").
Filter(c.rbacUsecase.CheckPerm("component", "detail")).
Filter(c.appCheckFilter).
Filter(c.componentCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("compName", "identifier of the component").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "OK", apis.DetailComponentResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.DetailComponentResponse{}))
ws.Route(ws.PUT("/{name}/components/{compName}").To(c.updateComponent).
ws.Route(ws.PUT("/{appName}/components/{compName}").To(c.updateComponent).
Doc("update component config").
Filter(c.rbacUsecase.CheckPerm("component", "update")).
Filter(c.appCheckFilter).
Filter(c.componentCheckFilter).
Param(ws.PathParameter("name", "identifier of the application").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application").DataType("string")).
Param(ws.PathParameter("compName", "identifier of the component").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.UpdateApplicationComponentRequest{}).
@@ -205,11 +224,12 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ComponentBase{}))
ws.Route(ws.DELETE("/{name}/components/{compName}").To(c.deleteComponent).
ws.Route(ws.DELETE("/{appName}/components/{compName}").To(c.deleteComponent).
Doc("delete a component").
Filter(c.rbacUsecase.CheckPerm("component", "delete")).
Filter(c.appCheckFilter).
Filter(c.componentCheckFilter).
Param(ws.PathParameter("name", "identifier of the application").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application").DataType("string")).
Param(ws.PathParameter("compName", "identifier of the component").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "OK", apis.EmptyResponse{}).
@@ -217,49 +237,54 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(404, "Not Found", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.GET("/{name}/policies").To(c.listApplicationPolicies).
ws.Route(ws.GET("/{appName}/policies").To(c.listApplicationPolicies).
Doc("list policy for application").
Filter(c.rbacUsecase.CheckPerm("policy", "list")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "OK", apis.ListApplicationPolicy{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ListApplicationPolicy{}))
ws.Route(ws.POST("/{name}/policies").To(c.createApplicationPolicy).
ws.Route(ws.POST("/{appName}/policies").To(c.createApplicationPolicy).
Doc("create policy for application").
Filter(c.rbacUsecase.CheckPerm("policy", "create")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.CreatePolicyRequest{}).
Returns(200, "OK", apis.PolicyBase{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.PolicyBase{}))
ws.Route(ws.GET("/{name}/policies/{policyName}").To(c.detailApplicationPolicy).
ws.Route(ws.GET("/{appName}/policies/{policyName}").To(c.detailApplicationPolicy).
Doc("detail policy for application").
Filter(c.rbacUsecase.CheckPerm("policy", "detail")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application").DataType("string")).
Param(ws.PathParameter("policyName", "identifier of the application policy").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "OK", apis.DetailPolicyResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.DetailPolicyResponse{}))
ws.Route(ws.DELETE("/{name}/policies/{policyName}").To(c.deleteApplicationPolicy).
ws.Route(ws.DELETE("/{appName}/policies/{policyName}").To(c.deleteApplicationPolicy).
Doc("detail policy for application").
Filter(c.rbacUsecase.CheckPerm("policy", "delete")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application").DataType("string")).
Param(ws.PathParameter("policyName", "identifier of the application policy").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "OK", apis.EmptyResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.PUT("/{name}/policies/{policyName}").To(c.updateApplicationPolicy).
ws.Route(ws.PUT("/{appName}/policies/{policyName}").To(c.updateApplicationPolicy).
Doc("update policy for application").
Filter(c.rbacUsecase.CheckPerm("policy", "update")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application").DataType("string")).
Param(ws.PathParameter("policyName", "identifier of the application policy").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.UpdatePolicyRequest{}).
@@ -267,11 +292,12 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.DetailPolicyResponse{}))
ws.Route(ws.POST("/{name}/components/{compName}/traits").To(c.addApplicationTrait).
ws.Route(ws.POST("/{appName}/components/{compName}/traits").To(c.addApplicationTrait).
Doc("add trait for a component").
Filter(c.rbacUsecase.CheckPerm("trait", "create")).
Filter(c.appCheckFilter).
Filter(c.componentCheckFilter).
Param(ws.PathParameter("name", "identifier of the application").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application").DataType("string")).
Param(ws.PathParameter("compName", "identifier of the component").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.CreateApplicationTraitRequest{}).
@@ -279,11 +305,12 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ApplicationTrait{}))
ws.Route(ws.PUT("/{name}/components/{compName}/traits/{traitType}").To(c.updateApplicationTrait).
ws.Route(ws.PUT("/{appName}/components/{compName}/traits/{traitType}").To(c.updateApplicationTrait).
Doc("update trait from a component").
Filter(c.rbacUsecase.CheckPerm("trait", "update")).
Filter(c.appCheckFilter).
Filter(c.componentCheckFilter).
Param(ws.PathParameter("name", "identifier of the application").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application").DataType("string")).
Param(ws.PathParameter("compName", "identifier of the component").DataType("string")).
Param(ws.PathParameter("traitType", "identifier of the type of trait").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
@@ -292,11 +319,12 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ApplicationTrait{}))
ws.Route(ws.DELETE("/{name}/components/{compName}/traits/{traitType}").To(c.deleteApplicationTrait).
ws.Route(ws.DELETE("/{appName}/components/{compName}/traits/{traitType}").To(c.deleteApplicationTrait).
Doc("delete trait from a component").
Filter(c.rbacUsecase.CheckPerm("trait", "delete")).
Filter(c.appCheckFilter).
Filter(c.componentCheckFilter).
Param(ws.PathParameter("name", "identifier of the application").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application").DataType("string")).
Param(ws.PathParameter("compName", "identifier of the component").DataType("string")).
Param(ws.PathParameter("traitType", "identifier of the type of trait").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
@@ -304,10 +332,11 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.GET("/{name}/revisions").To(c.listApplicationRevisions).
ws.Route(ws.GET("/{appName}/revisions").To(c.listApplicationRevisions).
Doc("list revisions for application").
Filter(c.rbacUsecase.CheckPerm("revision", "list")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Param(ws.QueryParameter("envName", "query identifier of the env").DataType("string")).
Param(ws.QueryParameter("status", "query identifier of the status").DataType("string")).
Param(ws.QueryParameter("page", "query the page number").DataType("integer")).
@@ -317,135 +346,148 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ListRevisionsResponse{}))
ws.Route(ws.GET("/{name}/revisions/{revision}").To(c.detailApplicationRevision).
ws.Route(ws.GET("/{appName}/revisions/{revision}").To(c.detailApplicationRevision).
Doc("detail revision for application").
Filter(c.rbacUsecase.CheckPerm("revision", "detail")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application").DataType("string")).
Param(ws.PathParameter("revision", "identifier of the application revision").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "OK", apis.DetailRevisionResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.DetailRevisionResponse{}))
ws.Route(ws.GET("/{name}/envs").To(c.listApplicationEnvs).
ws.Route(ws.GET("/{appName}/envs").To(c.listApplicationEnvs).
Doc("list policy for application").
Filter(c.rbacUsecase.CheckPerm("envBinding", "list")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "OK", apis.ListApplicationEnvBinding{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ListApplicationEnvBinding{}))
ws.Route(ws.POST("/{name}/envs").To(c.createApplicationEnv).
ws.Route(ws.POST("/{appName}/envs").To(c.createApplicationEnv).
Doc("creating an application environment ").
Filter(c.rbacUsecase.CheckPerm("envBinding", "create")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Reads(apis.CreateApplicationEnvbindingRequest{}).
Returns(200, "OK", apis.EnvBinding{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.PUT("/{name}/envs/{envName}").To(c.updateApplicationEnv).
ws.Route(ws.PUT("/{appName}/envs/{envName}").To(c.updateApplicationEnv).
Doc("set application differences in the specified environment").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("envBinding", "update")).
Filter(c.appCheckFilter).
Filter(c.envCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("envName", "identifier of the envBinding ").DataType("string")).
Reads(apis.PutApplicationEnvBindingRequest{}).
Returns(200, "OK", apis.EnvBinding{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.EnvBinding{}))
ws.Route(ws.DELETE("/{name}/envs/{envName}").To(c.deleteApplicationEnv).
ws.Route(ws.DELETE("/{appName}/envs/{envName}").To(c.deleteApplicationEnv).
Doc("delete an application environment ").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("envBinding", "delete")).
Filter(c.appCheckFilter).
Filter(c.envCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("envName", "identifier of the envBinding ").DataType("string")).
Returns(200, "OK", apis.EmptyResponse{}).
Returns(404, "Not Found", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.GET("/{name}/envs/{envName}/status").To(c.getApplicationStatus).
ws.Route(ws.GET("/{appName}/envs/{envName}/status").To(c.getApplicationStatus).
Doc("get application status").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("envBinding", "detail")).
Filter(c.appCheckFilter).
Filter(c.envCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("envName", "identifier of the application envbinding").DataType("string")).
Returns(200, "OK", apis.ApplicationStatusResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ApplicationStatusResponse{}))
ws.Route(ws.POST("/{name}/envs/{envName}/recycle").To(c.recycleApplicationEnv).
ws.Route(ws.POST("/{appName}/envs/{envName}/recycle").To(c.recycleApplicationEnv).
Doc("get application status").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("envBinding", "recycle")).
Filter(c.appCheckFilter).
Filter(c.envCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string").Required(true)).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string").Required(true)).
Param(ws.PathParameter("envName", "identifier of the application envbinding").DataType("string").Required(true)).
Returns(200, "OK", apis.EmptyResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.GET("/{name}/workflows").To(c.listApplicationWorkflows).
ws.Route(ws.GET("/{appName}/workflows").To(c.listApplicationWorkflows).
Doc("list application workflow").
Filter(c.rbacUsecase.CheckPerm("application/workflow", "list")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)).
Param(ws.PathParameter("appName", "identifier of the application.").DataType("string").Required(true)).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "OK", apis.ListWorkflowResponse{}).
Writes(apis.ListWorkflowResponse{}).Do(returns200, returns500))
ws.Route(ws.POST("/{name}/workflows").To(c.createOrUpdateApplicationWorkflow).
ws.Route(ws.POST("/{appName}/workflows").To(c.createOrUpdateApplicationWorkflow).
Doc("create application workflow").
Filter(c.rbacUsecase.CheckPerm("application/workflow", "create")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.CreateWorkflowRequest{}).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)).
Param(ws.PathParameter("appName", "identifier of the application.").DataType("string").Required(true)).
Returns(200, "create success", apis.DetailWorkflowResponse{}).
Returns(400, "create failure", bcode.Bcode{}).
Writes(apis.DetailWorkflowResponse{}).Do(returns200, returns500))
ws.Route(ws.GET("/{name}/workflows/{workflowName}").To(c.detailWorkflow).
ws.Route(ws.GET("/{appName}/workflows/{workflowName}").To(c.detailWorkflow).
Doc("detail application workflow").
Filter(c.rbacUsecase.CheckPerm("application/workflow", "detail")).
Filter(c.appCheckFilter).
Filter(c.workflowCheckFilter).
Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)).
Param(ws.PathParameter("appName", "identifier of the application.").DataType("string").Required(true)).
Param(ws.PathParameter("workflowName", "identifier of the workfloc.").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.workflowCheckFilter).
Returns(200, "create success", apis.DetailWorkflowResponse{}).
Writes(apis.DetailWorkflowResponse{}).Do(returns200, returns500))
ws.Route(ws.PUT("/{name}/workflows/{workflowName}").To(c.updateWorkflow).
ws.Route(ws.PUT("/{appName}/workflows/{workflowName}").To(c.updateWorkflow).
Doc("update application workflow config").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("application/workflow", "update")).
Filter(c.appCheckFilter).
Filter(c.workflowCheckFilter).
Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)).
Param(ws.PathParameter("appName", "identifier of the application.").DataType("string").Required(true)).
Param(ws.PathParameter("workflowName", "identifier of the workflow").DataType("string")).
Reads(apis.UpdateWorkflowRequest{}).
Returns(200, "OK", apis.DetailWorkflowResponse{}).
Writes(apis.DetailWorkflowResponse{}).Do(returns200, returns500))
ws.Route(ws.DELETE("/{name}/workflows/{workflowName}").To(c.deleteWorkflow).
ws.Route(ws.DELETE("/{appName}/workflows/{workflowName}").To(c.deleteWorkflow).
Doc("deletet workflow").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("application/workflow", "delete")).
Filter(c.appCheckFilter).
Filter(c.workflowCheckFilter).
Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)).
Param(ws.PathParameter("appName", "identifier of the application.").DataType("string").Required(true)).
Param(ws.PathParameter("workflowName", "identifier of the workflow").DataType("string")).
Returns(200, "OK", apis.EmptyResponse{}).
Writes(apis.EmptyResponse{}).Do(returns200, returns500))
ws.Route(ws.GET("/{name}/workflows/{workflowName}/records").To(c.listWorkflowRecords).
ws.Route(ws.GET("/{appName}/workflows/{workflowName}/records").To(c.listWorkflowRecords).
Doc("query application workflow execution record").
Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)).
Param(ws.PathParameter("appName", "identifier of the application.").DataType("string").Required(true)).
Param(ws.PathParameter("workflowName", "identifier of the workflow").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("application/workflow/record", "list")).
Filter(c.appCheckFilter).
Filter(c.workflowCheckFilter).
Param(ws.QueryParameter("page", "query the page number").DataType("integer")).
@@ -453,9 +495,10 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(200, "OK", apis.ListWorkflowRecordsResponse{}).
Writes(apis.ListWorkflowRecordsResponse{}).Do(returns200, returns500))
ws.Route(ws.GET("/{name}/workflows/{workflowName}/records/{record}").To(c.detailWorkflowRecord).
ws.Route(ws.GET("/{appName}/workflows/{workflowName}/records/{record}").To(c.detailWorkflowRecord).
Doc("query application workflow execution record detail").
Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)).
Filter(c.rbacUsecase.CheckPerm("application/workflow/record", "detail")).
Param(ws.PathParameter("appName", "identifier of the application.").DataType("string").Required(true)).
Param(ws.PathParameter("workflowName", "identifier of the workflow").DataType("string")).
Param(ws.PathParameter("record", "identifier of the workflow record").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
@@ -464,9 +507,10 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(200, "OK", apis.DetailWorkflowRecordResponse{}).
Writes(apis.DetailWorkflowRecordResponse{}).Do(returns200, returns500))
ws.Route(ws.GET("/{name}/workflows/{workflowName}/records/{record}/resume").To(c.resumeWorkflowRecord).
ws.Route(ws.GET("/{appName}/workflows/{workflowName}/records/{record}/resume").To(c.resumeWorkflowRecord).
Doc("resume suspend workflow record").
Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)).
Filter(c.rbacUsecase.CheckPerm("application/workflow/record", "resume")).
Param(ws.PathParameter("appName", "identifier of the application.").DataType("string").Required(true)).
Param(ws.PathParameter("workflowName", "identifier of the workflow").DataType("string")).
Param(ws.PathParameter("record", "identifier of the workflow record").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
@@ -476,9 +520,10 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.DetailWorkflowRecordResponse{}))
ws.Route(ws.GET("/{name}/workflows/{workflowName}/records/{record}/terminate").To(c.terminateWorkflowRecord).
ws.Route(ws.GET("/{appName}/workflows/{workflowName}/records/{record}/terminate").To(c.terminateWorkflowRecord).
Doc("terminate suspend workflow record").
Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)).
Filter(c.rbacUsecase.CheckPerm("application/workflow/record", "terminate")).
Param(ws.PathParameter("appName", "identifier of the application.").DataType("string").Required(true)).
Param(ws.PathParameter("workflowName", "identifier of the workflow").DataType("string")).
Param(ws.PathParameter("record", "identifier of the workflow record").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
@@ -488,9 +533,10 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.DetailWorkflowRecordResponse{}))
ws.Route(ws.GET("/{name}/workflows/{workflowName}/records/{record}/rollback").To(c.rollbackWorkflowRecord).
ws.Route(ws.GET("/{appName}/workflows/{workflowName}/records/{record}/rollback").To(c.rollbackWorkflowRecord).
Doc("rollback suspend application record").
Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)).
Filter(c.rbacUsecase.CheckPerm("application/workflow/record", "rollback")).
Param(ws.PathParameter("appName", "identifier of the application.").DataType("string").Required(true)).
Param(ws.PathParameter("workflowName", "identifier of the workflow").DataType("string")).
Param(ws.PathParameter("record", "identifier of the workflow record").DataType("string")).
Param(ws.QueryParameter("rollbackVersion", "identifier of the rollback revision").DataType("string")).
@@ -501,38 +547,42 @@ func (c *applicationWebService) GetWebService() *restful.WebService {
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.DetailWorkflowRecordResponse{}))
ws.Route(ws.GET("/{name}/records").To(c.listApplicationRecords).
ws.Route(ws.GET("/{appName}/records").To(c.listApplicationRecords).
Doc("list application records").
Param(ws.PathParameter("name", "identifier of the application.").DataType("string").Required(true)).
Filter(c.rbacUsecase.CheckPerm("application/workflow/record", "list")).
Param(ws.PathParameter("appName", "identifier of the application.").DataType("string").Required(true)).
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.appCheckFilter).
Returns(200, "OK", nil).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ListWorkflowRecordsResponse{}))
ws.Route(ws.POST("/{name}/compare").To(c.compareAppWithLatestRevision).
ws.Route(ws.POST("/{appName}/compare").To(c.compareAppWithLatestRevision).
Doc("compare application with env latest revision").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("application", "compare")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Returns(200, "OK", apis.ApplicationBase{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.AppCompareResponse{}))
ws.Route(ws.POST("/{name}/reset").To(c.resetAppToLatestRevision).
ws.Route(ws.POST("/{appName}/reset").To(c.resetAppToLatestRevision).
Doc("reset application to latest revision").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("application", "reset")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Returns(200, "OK", apis.AppResetResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.AppResetResponse{}))
ws.Route(ws.POST("/{name}/dry-run").To(c.dryRunAppOrRevision).
ws.Route(ws.POST("/{appName}/dry-run").To(c.dryRunAppOrRevision).
Doc("dry-run application to latest revision").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("application", "detail")).
Filter(c.appCheckFilter).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Param(ws.PathParameter("appName", "identifier of the application ").DataType("string")).
Returns(200, "OK", apis.AppDryRunResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.AppDryRunResponse{}))
@@ -568,8 +618,12 @@ func (c *applicationWebService) createApplication(req *restful.Request, res *res
}
func (c *applicationWebService) listApplications(req *restful.Request, res *restful.Response) {
var projetNames []string
if req.QueryParameter("project") != "" {
projetNames = append(projetNames, req.QueryParameter("project"))
}
apps, err := c.applicationUsecase.ListApplications(req.Request.Context(), apis.ListApplicationOptions{
Project: req.QueryParameter("project"),
Projects: projetNames,
Env: req.QueryParameter("env"),
TargetName: req.QueryParameter("targetName"),
Query: req.QueryParameter("query"),
@@ -962,13 +1016,13 @@ func (c *applicationWebService) getApplicationStatus(req *restful.Request, res *
}
func (c *applicationWebService) listApplicationRevisions(req *restful.Request, res *restful.Response) {
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
page, pageSize, err := utils.ExtractPagingParams(req, minPageSize, maxPageSize)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
revisions, err := c.applicationUsecase.ListRevisions(req.Request.Context(), req.PathParameter("name"), req.QueryParameter("envName"), req.QueryParameter("status"), page, pageSize)
revisions, err := c.applicationUsecase.ListRevisions(req.Request.Context(), app.Name, req.QueryParameter("envName"), req.QueryParameter("status"), page, pageSize)
if err != nil {
bcode.ReturnError(req, res, err)
return
@@ -980,7 +1034,8 @@ func (c *applicationWebService) listApplicationRevisions(req *restful.Request, r
}
func (c *applicationWebService) detailApplicationRevision(req *restful.Request, res *restful.Response) {
detail, err := c.applicationUsecase.DetailRevision(req.Request.Context(), req.PathParameter("name"), req.PathParameter("revision"))
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
detail, err := c.applicationUsecase.DetailRevision(req.Request.Context(), app.Name, req.PathParameter("revision"))
if err != nil {
bcode.ReturnError(req, res, err)
return
@@ -1064,7 +1119,7 @@ func (c *applicationWebService) deleteApplicationEnv(req *restful.Request, res *
}
func (c *applicationWebService) appCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
app, err := c.applicationUsecase.GetApplication(req.Request.Context(), req.PathParameter("name"))
app, err := c.applicationUsecase.GetApplication(req.Request.Context(), req.PathParameter("appName"))
if err != nil {
bcode.ReturnError(req, res, err)
return
@@ -1123,7 +1178,8 @@ func (c *applicationWebService) recycleApplicationEnv(req *restful.Request, res
}
func (c *applicationWebService) listApplicationRecords(req *restful.Request, res *restful.Response) {
records, err := c.applicationUsecase.ListRecords(req.Request.Context(), req.PathParameter("name"))
app := req.Request.Context().Value(&apis.CtxKeyApplication).(*model.Application)
records, err := c.applicationUsecase.ListRecords(req.Request.Context(), app.Name)
if err != nil {
bcode.ReturnError(req, res, err)
return
@@ -30,12 +30,14 @@ import (
type authenticationWebService struct {
authenticationUsecase usecase.AuthenticationUsecase
userUsecase usecase.UserUsecase
}
// NewAuthenticationWebService is the webservice of authentication
func NewAuthenticationWebService(authenticationUsecase usecase.AuthenticationUsecase) WebService {
func NewAuthenticationWebService(authenticationUsecase usecase.AuthenticationUsecase, userUsecase usecase.UserUsecase) WebService {
return &authenticationWebService{
authenticationUsecase: authenticationUsecase,
userUsecase: userUsecase,
}
}
@@ -56,26 +58,34 @@ func (c *authenticationWebService) GetWebService() *restful.WebService {
Returns(400, "", bcode.Bcode{}).
Writes(apis.LoginResponse{}))
ws.Route(ws.GET("/dexConfig").To(c.getDexConfig).
ws.Route(ws.GET("/dex_config").To(c.getDexConfig).
Doc("get Dex config").
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "", apis.DexConfigResponse{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.DexConfigResponse{}))
ws.Route(ws.GET("/refreshToken").To(c.refreshToken).
ws.Route(ws.GET("/refresh_token").To(c.refreshToken).
Doc("refresh token").
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "", apis.RefreshTokenResponse{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.RefreshTokenResponse{}))
ws.Route(ws.GET("/loginType").To(c.getLoginType).
ws.Route(ws.GET("/login_type").To(c.getLoginType).
Doc("get login type").
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "", apis.GetLoginTypeResponse{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.GetLoginTypeResponse{}))
ws.Route(ws.GET("/user_info").To(c.getLoginUserInfo).
Doc("get login user detail info").
Filter(authCheckFilter).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "", apis.LoginUserInfoResponse{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.LoginUserInfoResponse{}))
return ws
}
@@ -157,3 +167,15 @@ func (c *authenticationWebService) getLoginType(req *restful.Request, res *restf
return
}
}
func (c *authenticationWebService) getLoginUserInfo(req *restful.Request, res *restful.Response) {
info, err := c.userUsecase.DetailLoginUserInfo(req.Request.Context())
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(info); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
+15 -2
View File
@@ -29,11 +29,12 @@ import (
// ClusterWebService cluster manage webservice
type ClusterWebService struct {
clusterUsecase usecase.ClusterUsecase
rbacUsecase usecase.RBACUsecase
}
// NewClusterWebService new cluster webservice
func NewClusterWebService(clusterUsecase usecase.ClusterUsecase) *ClusterWebService {
return &ClusterWebService{clusterUsecase: clusterUsecase}
func NewClusterWebService(clusterUsecase usecase.ClusterUsecase, rbacUsecase usecase.RBACUsecase) *ClusterWebService {
return &ClusterWebService{clusterUsecase: clusterUsecase, rbacUsecase: rbacUsecase}
}
// GetWebService -
@@ -49,6 +50,7 @@ func (c *ClusterWebService) GetWebService() *restful.WebService {
ws.Route(ws.GET("/").To(c.listKubeClusters).
Doc("list all clusters").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("cluster", "list")).
Param(ws.QueryParameter("query", "Fuzzy search based on name or description").DataType("string")).
Param(ws.QueryParameter("page", "Page for paging").DataType("integer").DefaultValue("0")).
Param(ws.QueryParameter("pageSize", "PageSize for paging").DataType("integer").DefaultValue("20")).
@@ -60,6 +62,7 @@ func (c *ClusterWebService) GetWebService() *restful.WebService {
Doc("create cluster").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.CreateClusterRequest{}).
Filter(c.rbacUsecase.CheckPerm("cluster", "create")).
Returns(200, "OK", apis.ClusterBase{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.ClusterBase{}))
@@ -67,6 +70,7 @@ func (c *ClusterWebService) GetWebService() *restful.WebService {
ws.Route(ws.GET("/{clusterName}").To(c.getKubeCluster).
Doc("detail cluster info").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("cluster", "detail")).
Param(ws.PathParameter("clusterName", "identifier of the cluster").DataType("string")).
Returns(200, "OK", apis.DetailClusterResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
@@ -75,6 +79,7 @@ func (c *ClusterWebService) GetWebService() *restful.WebService {
ws.Route(ws.PUT("/{clusterName}").To(c.modifyKubeCluster).
Doc("modify cluster").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("cluster", "update")).
Param(ws.PathParameter("clusterName", "identifier of the cluster").DataType("string")).
Reads(apis.CreateClusterRequest{}).
Returns(200, "OK", apis.ClusterBase{}).
@@ -84,6 +89,7 @@ func (c *ClusterWebService) GetWebService() *restful.WebService {
ws.Route(ws.DELETE("/{clusterName}").To(c.deleteKubeCluster).
Doc("delete cluster").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("cluster", "delete")).
Param(ws.PathParameter("clusterName", "identifier of the cluster").DataType("string")).
Returns(200, "OK", apis.ClusterBase{}).
Returns(400, "Bad Request", bcode.Bcode{}).
@@ -94,6 +100,7 @@ func (c *ClusterWebService) GetWebService() *restful.WebService {
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("clusterName", "name of the target cluster").DataType("string")).
Reads(apis.CreateClusterNamespaceRequest{}).
Filter(c.rbacUsecase.CheckPerm("cluster/namespace", "create")).
Returns(200, "OK", apis.CreateClusterNamespaceResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.CreateClusterNamespaceResponse{}))
@@ -101,6 +108,7 @@ func (c *ClusterWebService) GetWebService() *restful.WebService {
ws.Route(ws.POST("/cloud_clusters/{provider}").To(c.listCloudClusters).
Doc("list cloud clusters").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("cluster", "create")).
Param(ws.PathParameter("provider", "identifier of the cloud provider").DataType("string")).
Param(ws.QueryParameter("page", "Page for paging").DataType("integer").DefaultValue("0")).
Param(ws.QueryParameter("pageSize", "PageSize for paging").DataType("integer").DefaultValue("20")).
@@ -112,6 +120,7 @@ func (c *ClusterWebService) GetWebService() *restful.WebService {
ws.Route(ws.POST("/cloud_clusters/{provider}/connect").To(c.connectCloudCluster).
Doc("create cluster from cloud cluster").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("cluster", "create")).
Param(ws.PathParameter("provider", "identifier of the cloud provider").DataType("string")).
Reads(apis.ConnectCloudClusterRequest{}).
Returns(200, "OK", apis.ClusterBase{}).
@@ -121,6 +130,7 @@ func (c *ClusterWebService) GetWebService() *restful.WebService {
ws.Route(ws.POST("/cloud_clusters/{provider}/create").To(c.createCloudCluster).
Doc("create cloud cluster").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("cluster", "create")).
Param(ws.PathParameter("provider", "identifier of the cloud provider").DataType("string").Required(true)).
Reads(apis.CreateCloudClusterRequest{}).
Returns(200, "OK", apis.CreateCloudClusterResponse{}).
@@ -130,6 +140,7 @@ func (c *ClusterWebService) GetWebService() *restful.WebService {
ws.Route(ws.GET("/cloud_clusters/{provider}/creation/{cloudClusterName}").To(c.getCloudClusterCreationStatus).
Doc("check cloud cluster create status").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("cluster", "create")).
Param(ws.PathParameter("provider", "identifier of the cloud provider").DataType("string")).
Param(ws.PathParameter("cloudClusterName", "identifier for cloud cluster which is creating").DataType("string")).
Returns(200, "OK", apis.CreateCloudClusterResponse{}).
@@ -138,6 +149,7 @@ func (c *ClusterWebService) GetWebService() *restful.WebService {
ws.Route(ws.GET("/cloud_clusters/{provider}/creation").To(c.listCloudClusterCreation).
Doc("list cloud cluster creation").
Filter(c.rbacUsecase.CheckPerm("cluster", "create")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("provider", "identifier of the cloud provider").DataType("string")).
Returns(200, "OK", apis.ListCloudClusterCreationResponse{}).
@@ -147,6 +159,7 @@ func (c *ClusterWebService) GetWebService() *restful.WebService {
ws.Route(ws.DELETE("/cloud_clusters/{provider}/creation/{cloudClusterName}").To(c.deleteCloudClusterCreation).
Doc("delete cloud cluster creation").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("cluster", "create")).
Param(ws.PathParameter("provider", "identifier of the cloud provider").DataType("string")).
Param(ws.PathParameter("cloudClusterName", "identifier for cloud cluster which is creating").DataType("string")).
Returns(200, "OK", apis.CreateCloudClusterResponse{}).
+9 -4
View File
@@ -27,6 +27,7 @@ import (
type definitionWebservice struct {
definitionUsecase usecase.DefinitionUsecase
rbacUsecase usecase.RBACUsecase
}
func (d *definitionWebservice) GetWebService() *restful.WebService {
@@ -41,15 +42,18 @@ func (d *definitionWebservice) GetWebService() *restful.WebService {
ws.Route(ws.GET("/").To(d.listDefinitions).
Doc("list all definitions").
Metadata(restfulspec.KeyOpenAPITags, tags).
// TODO: provide project scope api for query definition list
// Filter(d.rbacUsecase.CheckPerm("definition", "list")).
Param(ws.QueryParameter("type", "query the definition type").DataType("string").Required(true).AllowableValues(map[string]string{"component": "", "trait": "", "workflowstep": ""})).
Param(ws.QueryParameter("envName", "if specified, query the definition supported by the env.").DataType("string")).
Param(ws.QueryParameter("appliedWorkload", "if specified, query the trait definition applied to the workload.").DataType("string")).
Returns(200, "OK", apis.ListDefinitionResponse{}).
Writes(apis.ListDefinitionResponse{}).Do(returns200, returns500))
ws.Route(ws.GET("/{name}").To(d.detailDefinition).
ws.Route(ws.GET("/{definitionName}").To(d.detailDefinition).
Doc("detail definition").
Param(ws.PathParameter("name", "identifier of the definition").DataType("string")).
// Filter(d.rbacUsecase.CheckPerm("definition", "detail")).
Param(ws.PathParameter("definitionName", "identifier of the definition").DataType("string")).
Param(ws.QueryParameter("type", "query the definition type").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "create success", apis.DetailDefinitionResponse{}).
@@ -60,9 +64,10 @@ func (d *definitionWebservice) GetWebService() *restful.WebService {
}
// NewDefinitionWebservice new definition webservice
func NewDefinitionWebservice(du usecase.DefinitionUsecase) WebService {
func NewDefinitionWebservice(du usecase.DefinitionUsecase, rbacUsecase usecase.RBACUsecase) WebService {
return &definitionWebservice{
definitionUsecase: du,
rbacUsecase: rbacUsecase,
}
}
@@ -79,7 +84,7 @@ func (d *definitionWebservice) listDefinitions(req *restful.Request, res *restfu
}
func (d *definitionWebservice) detailDefinition(req *restful.Request, res *restful.Response) {
definition, err := d.definitionUsecase.DetailDefinition(req.Request.Context(), req.PathParameter("name"), req.QueryParameter("type"))
definition, err := d.definitionUsecase.DetailDefinition(req.Request.Context(), req.PathParameter("definitionName"), req.QueryParameter("type"))
if err != nil {
bcode.ReturnError(req, res, err)
return
+16 -10
View File
@@ -28,13 +28,14 @@ import (
)
type envWebService struct {
envUsecase usecase.EnvUsecase
appUsecase usecase.ApplicationUsecase
envUsecase usecase.EnvUsecase
appUsecase usecase.ApplicationUsecase
rbacUsecase usecase.RBACUsecase
}
// NewEnvWebService new env webservice
func NewEnvWebService(envUsecase usecase.EnvUsecase, appUseCase usecase.ApplicationUsecase) WebService {
return &envWebService{envUsecase: envUsecase, appUsecase: appUseCase}
func NewEnvWebService(envUsecase usecase.EnvUsecase, appUseCase usecase.ApplicationUsecase, rbacUsecase usecase.RBACUsecase) WebService {
return &envWebService{envUsecase: envUsecase, appUsecase: appUseCase, rbacUsecase: rbacUsecase}
}
func (n *envWebService) GetWebService() *restful.WebService {
@@ -49,6 +50,8 @@ func (n *envWebService) GetWebService() *restful.WebService {
ws.Route(ws.GET("/").To(n.list).
Operation("envlist").
Doc("list all envs").
// This api will filter the environments by user's permissions
// Filter(n.rbacUsecase.CheckPerm("environment", "list")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "OK", apis.ListEnvResponse{}).
Writes(apis.ListEnvResponse{}))
@@ -56,25 +59,28 @@ func (n *envWebService) GetWebService() *restful.WebService {
ws.Route(ws.POST("/").To(n.create).
Operation("envcreate").
Doc("create an env").
Filter(n.rbacUsecase.CheckPerm("environment", "create")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.CreateEnvRequest{}).
Returns(200, "OK", apis.Env{}).
Writes(apis.Env{}))
ws.Route(ws.PUT("/{name}").To(n.update).
ws.Route(ws.PUT("/{envName}").To(n.update).
Operation("envupdate").
Doc("update an env").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Filter(n.rbacUsecase.CheckPerm("environment", "update")).
Param(ws.PathParameter("envName", "identifier of the application ").DataType("string")).
Reads(apis.CreateEnvRequest{}).
Returns(200, "OK", apis.Env{}).
Writes(apis.Env{}))
ws.Route(ws.DELETE("/{name}").To(n.delete).
ws.Route(ws.DELETE("/{envName}").To(n.delete).
Operation("envdelete").
Doc("delete one env").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("name", "identifier of the application ").DataType("string")).
Filter(n.rbacUsecase.CheckPerm("environment", "delete")).
Param(ws.PathParameter("envName", "identifier of the application ").DataType("string")).
Returns(200, "OK", apis.EmptyResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
@@ -103,7 +109,7 @@ func (n *envWebService) list(req *restful.Request, res *restful.Response) {
// it will prevent the deletion if there's still application in it.
func (n *envWebService) delete(req *restful.Request, res *restful.Response) {
envname := req.PathParameter("name")
envname := req.PathParameter("envName")
ctx := req.Request.Context()
lists, err := n.appUsecase.ListApplications(ctx, apis.ListApplicationOptions{Env: envname})
@@ -166,7 +172,7 @@ func (n *envWebService) update(req *restful.Request, res *restful.Response) {
return
}
env, err := n.envUsecase.UpdateEnv(req.Request.Context(), req.PathParameter("name"), updateReq)
env, err := n.envUsecase.UpdateEnv(req.Request.Context(), req.PathParameter("envName"), updateReq)
if err != nil {
bcode.ReturnError(req, res, err)
return
@@ -28,12 +28,14 @@ import (
type oamApplicationWebService struct {
oamApplicationUsecase usecase.OAMApplicationUsecase
rbacUsecase usecase.RBACUsecase
}
// NewOAMApplication new oam application
func NewOAMApplication(oamApplicationUsecase usecase.OAMApplicationUsecase) WebService {
func NewOAMApplication(oamApplicationUsecase usecase.OAMApplicationUsecase, rbacUsecase usecase.RBACUsecase) WebService {
return &oamApplicationWebService{
oamApplicationUsecase: oamApplicationUsecase,
rbacUsecase: rbacUsecase,
}
}
@@ -49,6 +51,7 @@ func (c *oamApplicationWebService) GetWebService() *restful.WebService {
ws.Route(ws.GET("/namespaces/{namespace}/applications/{appname}").To(c.getApplication).
Doc("get the specified oam application in the specified namespace").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("application", "detail")).
Param(ws.PathParameter("namespace", "identifier of the namespace").DataType("string")).
Param(ws.PathParameter("appname", "identifier of the oam application").DataType("string")).
Returns(200, "OK", apis.ApplicationResponse{}).
@@ -57,6 +60,7 @@ func (c *oamApplicationWebService) GetWebService() *restful.WebService {
ws.Route(ws.POST("/namespaces/{namespace}/applications/{appname}").To(c.createOrUpdateApplication).
Doc("create or update oam application in the specified namespace").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("application", "deploy")).
Param(ws.PathParameter("namespace", "identifier of the namespace").DataType("string")).
Param(ws.PathParameter("appname", "identifier of the oam application").DataType("string")).
Reads(apis.ApplicationRequest{}))
@@ -65,9 +69,11 @@ func (c *oamApplicationWebService) GetWebService() *restful.WebService {
Operation("deleteOAMApplication").
Doc("create or update oam application in the specified namespace").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("application", "delete")).
Param(ws.PathParameter("namespace", "identifier of the namespace").DataType("string")).
Param(ws.PathParameter("appname", "identifier of the oam application").DataType("string")))
ws.Filter(authCheckFilter)
return ws
}
+402 -3
View File
@@ -28,12 +28,14 @@ import (
)
type projectWebService struct {
rbacUsecase usecase.RBACUsecase
projectUsecase usecase.ProjectUsecase
targetUsecase usecase.TargetUsecase
}
// NewProjectWebService new project webservice
func NewProjectWebService(projectUsecase usecase.ProjectUsecase) WebService {
return &projectWebService{projectUsecase: projectUsecase}
func NewProjectWebService(projectUsecase usecase.ProjectUsecase, rbacUsecase usecase.RBACUsecase, targetUsecase usecase.TargetUsecase) WebService {
return &projectWebService{projectUsecase: projectUsecase, rbacUsecase: rbacUsecase, targetUsecase: targetUsecase}
}
func (n *projectWebService) GetWebService() *restful.WebService {
@@ -48,16 +50,132 @@ func (n *projectWebService) GetWebService() *restful.WebService {
ws.Route(ws.GET("/").To(n.listprojects).
Doc("list all projects").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(n.rbacUsecase.CheckPerm("project", "list")).
Returns(200, "OK", apis.ListProjectResponse{}).
Writes(apis.ListProjectResponse{}))
ws.Route(ws.POST("/").To(n.createproject).
Doc("create a project").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(n.rbacUsecase.CheckPerm("project", "create")).
Reads(apis.CreateProjectRequest{}).
Returns(200, "OK", apis.ProjectBase{}).
Writes(apis.ProjectBase{}))
ws.Route(ws.GET("/{projectName}").To(n.detailProject).
Doc("detail a project").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
Filter(n.rbacUsecase.CheckPerm("project", "detail")).
Returns(200, "OK", apis.ProjectBase{}).
Writes(apis.ProjectBase{}))
ws.Route(ws.PUT("/{projectName}").To(n.updateProject).
Doc("update a project").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
Filter(n.rbacUsecase.CheckPerm("project", "update")).
Reads(apis.UpdateProjectRequest{}).
Returns(200, "OK", apis.ProjectBase{}).
Writes(apis.ProjectBase{}))
ws.Route(ws.DELETE("/{projectName}").To(n.deleteProject).
Doc("delete a project").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
Filter(n.rbacUsecase.CheckPerm("project", "delete")).
Returns(200, "OK", apis.EmptyResponse{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.GET("/{projectName}/targets").To(n.listProjectTargets).
Doc("get targets list belong to a project").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
Filter(n.rbacUsecase.CheckPerm("project", "detail")).
Returns(200, "OK", apis.EmptyResponse{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.POST("/{projectName}/users").To(n.createProjectUser).
Doc("add a user to a project").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
Filter(n.rbacUsecase.CheckPerm("project/projectUser", "create")).
Reads(apis.AddProjectUserRequest{}).
Returns(200, "OK", apis.ProjectUserBase{}).
Writes(apis.ProjectUserBase{}))
ws.Route(ws.GET("/{projectName}/users").To(n.listProjectUser).
Doc("list all users belong to a project").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
Filter(n.rbacUsecase.CheckPerm("project/projectUser", "list")).
Returns(200, "OK", apis.ListProjectUsersResponse{}).
Writes(apis.ListProjectUsersResponse{}))
ws.Route(ws.PUT("/{projectName}/users/{userName}").To(n.updateProjectUser).
Doc("add a user to a project").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.UpdateProjectUserRequest{}).
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
Param(ws.PathParameter("userName", "identifier of the project user").DataType("string")).
Filter(n.rbacUsecase.CheckPerm("project/projectUser", "create")).
Returns(200, "OK", apis.ProjectUserBase{}).
Writes(apis.ProjectUserBase{}))
ws.Route(ws.DELETE("/{projectName}/users/{userName}").To(n.deleteProjectUser).
Doc("delete a user from a project").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.UpdateProjectUserRequest{}).
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
Param(ws.PathParameter("userName", "identifier of the project user").DataType("string")).
Filter(n.rbacUsecase.CheckPerm("project/projectUser", "delete")).
Returns(200, "OK", apis.EmptyResponse{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.GET("/{projectName}/roles").To(n.listProjectRoles).
Doc("list all project level roles").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
Filter(n.rbacUsecase.CheckPerm("project/role", "list")).
Returns(200, "OK", apis.ListRolesResponse{}).
Writes(apis.ListRolesResponse{}))
ws.Route(ws.POST("/{projectName}/roles").To(n.createProjectRole).
Doc("create project level role").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
Filter(n.rbacUsecase.CheckPerm("project/role", "create")).
Returns(200, "OK", apis.RoleBase{}).
Reads(apis.CreateRoleRequest{}).
Writes(apis.RoleBase{}))
ws.Route(ws.PUT("/{projectName}/roles/{roleName}").To(n.updateProjectRole).
Doc("update project level role").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
Param(ws.PathParameter("roleName", "identifier of the project role").DataType("string")).
Filter(n.rbacUsecase.CheckPerm("project/role", "update")).
Reads(apis.UpdateRoleRequest{}).
Returns(200, "OK", apis.RoleBase{}).
Writes(apis.RoleBase{}))
ws.Route(ws.DELETE("/{projectName}/roles/{roleName}").To(n.deleteProjectRole).
Doc("delete project level role").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
Param(ws.PathParameter("roleName", "identifier of the project role").DataType("string")).
Filter(n.rbacUsecase.CheckPerm("project/role", "delete")).
Returns(200, "OK", apis.EmptyResponse{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.GET("/{projectName}/permissions").To(n.listProjectPermissions).
Doc("list all project level perm policies").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("projectName", "identifier of the project").DataType("string")).
Filter(n.rbacUsecase.CheckPerm("project/permission", "list")).
Returns(200, "OK", []apis.PermissionBase{}).
Writes([]apis.PermissionBase{}))
ws.Filter(authCheckFilter)
return ws
}
@@ -93,7 +211,7 @@ func (n *projectWebService) createproject(req *restful.Request, res *restful.Res
// Call the usecase layer code
projectBase, err := n.projectUsecase.CreateProject(req.Request.Context(), createReq)
if err != nil {
log.Logger.Errorf("create application failure %s", err.Error())
log.Logger.Errorf("create project failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
@@ -104,3 +222,284 @@ func (n *projectWebService) createproject(req *restful.Request, res *restful.Res
return
}
}
func (n *projectWebService) updateProject(req *restful.Request, res *restful.Response) {
// Verify the validity of parameters
var updateReq apis.UpdateProjectRequest
if err := req.ReadEntity(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
// Call the usecase layer code
projectBase, err := n.projectUsecase.UpdateProject(req.Request.Context(), req.PathParameter("projectName"), updateReq)
if err != nil {
log.Logger.Errorf("update project failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(projectBase); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (n *projectWebService) detailProject(req *restful.Request, res *restful.Response) {
project, err := n.projectUsecase.DetailProject(req.Request.Context(), req.PathParameter("projectName"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(project); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (n *projectWebService) deleteProject(req *restful.Request, res *restful.Response) {
err := n.projectUsecase.DeleteProject(req.Request.Context(), req.PathParameter("projectName"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (n *projectWebService) listProjectTargets(req *restful.Request, res *restful.Response) {
project, err := n.projectUsecase.GetProject(req.Request.Context(), req.PathParameter("projectName"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
projects, err := n.targetUsecase.ListTargets(req.Request.Context(), 0, 0, project.Name)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(projects); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (n *projectWebService) createProjectUser(req *restful.Request, res *restful.Response) {
// Verify the validity of parameters
var createReq apis.AddProjectUserRequest
if err := req.ReadEntity(&createReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&createReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if len(createReq.UserRoles) == 0 {
bcode.ReturnError(req, res, bcode.ErrProjectRoleCheckFailure)
return
}
// Call the usecase layer code
userBase, err := n.projectUsecase.AddProjectUser(req.Request.Context(), req.PathParameter("projectName"), createReq)
if err != nil {
log.Logger.Errorf("create project user failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(userBase); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (n *projectWebService) listProjectUser(req *restful.Request, res *restful.Response) {
page, pageSize, err := utils.ExtractPagingParams(req, minPageSize, maxPageSize)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
// Call the usecase layer code
users, err := n.projectUsecase.ListProjectUser(req.Request.Context(), req.PathParameter("projectName"), page, pageSize)
if err != nil {
log.Logger.Errorf("list project users failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(users); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (n *projectWebService) updateProjectUser(req *restful.Request, res *restful.Response) {
// Verify the validity of parameters
var updateReq apis.UpdateProjectUserRequest
if err := req.ReadEntity(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if len(updateReq.UserRoles) == 0 {
bcode.ReturnError(req, res, bcode.ErrProjectRoleCheckFailure)
return
}
// Call the usecase layer code
userBase, err := n.projectUsecase.UpdateProjectUser(req.Request.Context(), req.PathParameter("projectName"), req.PathParameter("userName"), updateReq)
if err != nil {
log.Logger.Errorf("update project user failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(userBase); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (n *projectWebService) deleteProjectUser(req *restful.Request, res *restful.Response) {
// Call the usecase layer code
err := n.projectUsecase.DeleteProjectUser(req.Request.Context(), req.PathParameter("projectName"), req.PathParameter("userName"))
if err != nil {
log.Logger.Errorf("delete project user failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (n *projectWebService) listProjectRoles(req *restful.Request, res *restful.Response) {
if req.PathParameter("projectName") == "" {
bcode.ReturnError(req, res, bcode.ErrProjectIsNotExist)
return
}
page, pageSize, err := utils.ExtractPagingParams(req, minPageSize, maxPageSize)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
roles, err := n.rbacUsecase.ListRole(req.Request.Context(), req.PathParameter("projectName"), page, pageSize)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(roles); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (n *projectWebService) createProjectRole(req *restful.Request, res *restful.Response) {
if req.PathParameter("projectName") == "" {
bcode.ReturnError(req, res, bcode.ErrProjectIsNotExist)
return
}
// Verify the validity of parameters
var createReq apis.CreateRoleRequest
if err := req.ReadEntity(&createReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&createReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
// Call the usecase layer code
projectBase, err := n.rbacUsecase.CreateRole(req.Request.Context(), req.PathParameter("projectName"), createReq)
if err != nil {
log.Logger.Errorf("create role failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(projectBase); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (n *projectWebService) updateProjectRole(req *restful.Request, res *restful.Response) {
if req.PathParameter("projectName") == "" {
bcode.ReturnError(req, res, bcode.ErrProjectIsNotExist)
return
}
// Verify the validity of parameters
var updateReq apis.UpdateRoleRequest
if err := req.ReadEntity(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
// Call the usecase layer code
roleBase, err := n.rbacUsecase.UpdateRole(req.Request.Context(), req.PathParameter("projectName"), req.PathParameter("roleName"), updateReq)
if err != nil {
log.Logger.Errorf("update role failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(roleBase); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (n *projectWebService) deleteProjectRole(req *restful.Request, res *restful.Response) {
if req.PathParameter("projectName") == "" {
bcode.ReturnError(req, res, bcode.ErrProjectIsNotExist)
return
}
err := n.rbacUsecase.DeleteRole(req.Request.Context(), req.PathParameter("projectName"), req.PathParameter("roleName"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (n *projectWebService) listProjectPermissions(req *restful.Request, res *restful.Response) {
if req.PathParameter("projectName") == "" {
bcode.ReturnError(req, res, bcode.ErrProjectIsNotExist)
return
}
policies, err := n.rbacUsecase.ListPermissions(req.Request.Context(), req.PathParameter("projectName"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(policies); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
+181
View File
@@ -0,0 +1,181 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package webservice
import (
restfulspec "github.com/emicklei/go-restful-openapi/v2"
"github.com/emicklei/go-restful/v3"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
)
type rbacWebService struct {
rbacUsecase usecase.RBACUsecase
}
// NewRBACWebService new rbac webservice
func NewRBACWebService(rbacUsecase usecase.RBACUsecase) WebService {
return &rbacWebService{rbacUsecase: rbacUsecase}
}
func (r *rbacWebService) GetWebService() *restful.WebService {
ws := new(restful.WebService)
ws.Path(versionPrefix).
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML).
Doc("api for rbac")
tags := []string{"rbac"}
ws.Route(ws.GET("/roles").To(r.listPlatformRoles).
Doc("list all platform level roles").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(r.rbacUsecase.CheckPerm("role", "list")).
Returns(200, "OK", apis.ListRolesResponse{}).
Writes(apis.ListRolesResponse{}))
ws.Route(ws.POST("/roles").To(r.createPlatformRole).
Doc("create platform level role").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(r.rbacUsecase.CheckPerm("role", "create")).
Returns(200, "OK", apis.RoleBase{}).
Reads(apis.CreateRoleRequest{}).
Writes(apis.RoleBase{}))
ws.Route(ws.PUT("/roles/{roleName}").To(r.updatePlatformRole).
Doc("update platform level role").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(r.rbacUsecase.CheckPerm("role", "update")).
Reads(apis.UpdateRoleRequest{}).
Returns(200, "OK", apis.RoleBase{}).
Writes(apis.RoleBase{}))
ws.Route(ws.DELETE("/roles/{roleName}").To(r.deletePlatformRole).
Doc("update platform level role").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(r.rbacUsecase.CheckPerm("role", "delete")).
Returns(200, "OK", apis.EmptyResponse{}).
Writes(apis.EmptyResponse{}))
ws.Route(ws.GET("/permissions").To(r.listPlatformPermissions).
Doc("list all project level perm policies").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(r.rbacUsecase.CheckPerm("permission", "list")).
Returns(200, "OK", []apis.PermissionBase{}).
Writes([]apis.PermissionBase{}))
ws.Filter(authCheckFilter)
return ws
}
func (r *rbacWebService) listPlatformRoles(req *restful.Request, res *restful.Response) {
page, pageSize, err := utils.ExtractPagingParams(req, minPageSize, maxPageSize)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
roles, err := r.rbacUsecase.ListRole(req.Request.Context(), "", page, pageSize)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(roles); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (r *rbacWebService) createPlatformRole(req *restful.Request, res *restful.Response) {
// Verify the validity of parameters
var createReq apis.CreateRoleRequest
if err := req.ReadEntity(&createReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&createReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
// Call the usecase layer code
projectBase, err := r.rbacUsecase.CreateRole(req.Request.Context(), "", createReq)
if err != nil {
log.Logger.Errorf("create role failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(projectBase); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (r *rbacWebService) updatePlatformRole(req *restful.Request, res *restful.Response) {
// Verify the validity of parameters
var updateReq apis.UpdateRoleRequest
if err := req.ReadEntity(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := validate.Struct(&updateReq); err != nil {
bcode.ReturnError(req, res, err)
return
}
// Call the usecase layer code
roleBase, err := r.rbacUsecase.UpdateRole(req.Request.Context(), "", req.PathParameter("roleName"), updateReq)
if err != nil {
log.Logger.Errorf("update role failure %s", err.Error())
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(roleBase); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (r *rbacWebService) deletePlatformRole(req *restful.Request, res *restful.Response) {
err := r.rbacUsecase.DeleteRole(req.Request.Context(), "", req.PathParameter("roleName"))
if err != nil {
bcode.ReturnError(req, res, err)
return
}
// Write back response data
if err := res.WriteEntity(apis.EmptyResponse{}); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (r *rbacWebService) listPlatformPermissions(req *restful.Request, res *restful.Response) {
policies, err := r.rbacUsecase.ListPermissions(req.Request.Context(), "")
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(policies); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
+5 -18
View File
@@ -26,12 +26,13 @@ import (
)
type systemInfoWebService struct {
useCase usecase.SystemInfoUsecase
useCase usecase.SystemInfoUsecase
rbacUsecase usecase.RBACUsecase
}
// NewSystemInfoWebService return systemInfo webservice
func NewSystemInfoWebService(systemInfoUseCase usecase.SystemInfoUsecase) WebService {
return &systemInfoWebService{useCase: systemInfoUseCase}
func NewSystemInfoWebService(systemInfoUseCase usecase.SystemInfoUsecase, rbacUsecase usecase.RBACUsecase) WebService {
return &systemInfoWebService{useCase: systemInfoUseCase, rbacUsecase: rbacUsecase}
}
// GetWebService return systemInfo webservice
@@ -50,17 +51,11 @@ func (u systemInfoWebService) GetWebService() *restful.WebService {
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.SystemInfoResponse{}))
// Delete
ws.Route(ws.DELETE("/").To(u.deleteSystemInfo).
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "OK", apis.SystemInfoResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.SystemInfoResponse{}))
// Post
ws.Route(ws.PUT("/").To(u.updateSystemInfo).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.SystemInfoRequest{}).
Filter(u.rbacUsecase.CheckPerm("systemSetting", "update")).
Returns(200, "OK", apis.SystemInfoResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.SystemInfoResponse{}))
@@ -107,11 +102,3 @@ func (u systemInfoWebService) updateSystemInfo(req *restful.Request, res *restfu
return
}
}
func (u systemInfoWebService) deleteSystemInfo(req *restful.Request, res *restful.Response) {
err := u.useCase.DeleteSystemInfo(req.Request.Context())
if err != nil {
bcode.ReturnError(req, res, err)
return
}
}
+18 -10
View File
@@ -35,10 +35,11 @@ import (
)
// NewTargetWebService new Target webservice
func NewTargetWebService(targetUsecase usecase.TargetUsecase, applicationUsecase usecase.ApplicationUsecase) WebService {
func NewTargetWebService(targetUsecase usecase.TargetUsecase, applicationUsecase usecase.ApplicationUsecase, rbacUsecase usecase.RBACUsecase) WebService {
return &TargetWebService{
TargetUsecase: targetUsecase,
applicationUsecase: applicationUsecase,
rbacUsecase: rbacUsecase,
}
}
@@ -46,6 +47,7 @@ func NewTargetWebService(targetUsecase usecase.TargetUsecase, applicationUsecase
type TargetWebService struct {
TargetUsecase usecase.TargetUsecase
applicationUsecase usecase.ApplicationUsecase
rbacUsecase usecase.RBACUsecase
}
// GetWebService get web service
@@ -61,8 +63,10 @@ func (dt *TargetWebService) GetWebService() *restful.WebService {
ws.Route(ws.GET("/").To(dt.listTargets).
Doc("list Target").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(dt.rbacUsecase.CheckPerm("target", "list")).
Param(ws.QueryParameter("page", "Page for paging").DataType("integer")).
Param(ws.QueryParameter("pageSize", "PageSize for paging").DataType("integer")).
Param(ws.QueryParameter("project", "list targets by project name").DataType("string")).
Returns(200, "OK", apis.ListTargetResponse{}).
Writes(apis.ListTargetResponse{}).Do(returns200, returns500))
@@ -70,32 +74,36 @@ func (dt *TargetWebService) GetWebService() *restful.WebService {
Doc("create Target").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.CreateTargetRequest{}).
Filter(dt.rbacUsecase.CheckPerm("target", "create")).
Returns(200, "create success", apis.DetailTargetResponse{}).
Returns(400, "create failure", bcode.Bcode{}).
Writes(apis.DetailTargetResponse{}).Do(returns200, returns500))
ws.Route(ws.GET("/{name}").To(dt.detailTarget).
ws.Route(ws.GET("/{targetName}").To(dt.detailTarget).
Doc("detail Target").
Param(ws.PathParameter("name", "identifier of the Target.").DataType("string")).
Param(ws.PathParameter("targetName", "identifier of the Target.").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(dt.targetCheckFilter).
Filter(dt.rbacUsecase.CheckPerm("target", "detail")).
Returns(200, "create success", apis.DetailTargetResponse{}).
Writes(apis.DetailTargetResponse{}).Do(returns200, returns500))
ws.Route(ws.PUT("/{name}").To(dt.updateTarget).
ws.Route(ws.PUT("/{targetName}").To(dt.updateTarget).
Doc("update application Target config").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(dt.targetCheckFilter).
Param(ws.PathParameter("name", "identifier of the Target").DataType("string")).
Param(ws.PathParameter("targetName", "identifier of the Target").DataType("string")).
Reads(apis.UpdateTargetRequest{}).
Filter(dt.rbacUsecase.CheckPerm("target", "update")).
Returns(200, "OK", apis.DetailTargetResponse{}).
Writes(apis.DetailTargetResponse{}).Do(returns200, returns500))
ws.Route(ws.DELETE("/{name}").To(dt.deleteTarget).
ws.Route(ws.DELETE("/{targetName}").To(dt.deleteTarget).
Doc("deletet Target").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(dt.targetCheckFilter).
Param(ws.PathParameter("name", "identifier of the Target").DataType("string")).
Filter(dt.rbacUsecase.CheckPerm("target", "delete")).
Param(ws.PathParameter("targetName", "identifier of the Target").DataType("string")).
Returns(200, "OK", apis.EmptyResponse{}).
Writes(apis.EmptyResponse{}).Do(returns200, returns500))
@@ -129,7 +137,7 @@ func (dt *TargetWebService) createTarget(req *restful.Request, res *restful.Resp
}
func (dt *TargetWebService) targetCheckFilter(req *restful.Request, res *restful.Response, chain *restful.FilterChain) {
Target, err := dt.TargetUsecase.GetTarget(req.Request.Context(), req.PathParameter("name"))
Target, err := dt.TargetUsecase.GetTarget(req.Request.Context(), req.PathParameter("targetName"))
if err != nil {
bcode.ReturnError(req, res, err)
return
@@ -175,7 +183,7 @@ func (dt *TargetWebService) updateTarget(req *restful.Request, res *restful.Resp
}
func (dt *TargetWebService) deleteTarget(req *restful.Request, res *restful.Response) {
TargetName := req.PathParameter("name")
TargetName := req.PathParameter("targetName")
// Target in use, can't be deleted
applications, err := dt.applicationUsecase.ListApplications(req.Request.Context(), apis.ListApplicationOptions{TargetName: TargetName})
if err != nil {
@@ -204,7 +212,7 @@ func (dt *TargetWebService) listTargets(req *restful.Request, res *restful.Respo
bcode.ReturnError(req, res, err)
return
}
Targets, err := dt.TargetUsecase.ListTargets(req.Request.Context(), page, pageSize)
Targets, err := dt.TargetUsecase.ListTargets(req.Request.Context(), page, pageSize, req.QueryParameter("project"))
if err != nil {
bcode.ReturnError(req, res, err)
return
+10 -1
View File
@@ -31,12 +31,14 @@ import (
type userWebService struct {
userUsecase usecase.UserUsecase
rbacUsecase usecase.RBACUsecase
}
// NewUserWebService is the webservice of user
func NewUserWebService(userUsecase usecase.UserUsecase) WebService {
func NewUserWebService(userUsecase usecase.UserUsecase, rbacUsecase usecase.RBACUsecase) WebService {
return &userWebService{
userUsecase: userUsecase,
rbacUsecase: rbacUsecase,
}
}
@@ -52,6 +54,7 @@ func (c *userWebService) GetWebService() *restful.WebService {
ws.Route(ws.GET("/").To(c.listUser).
Doc("list users").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("user", "list")).
Param(ws.QueryParameter("page", "query the page number").DataType("integer")).
Param(ws.QueryParameter("pageSize", "query the page size number").DataType("integer")).
Param(ws.QueryParameter("name", "fuzzy search based on name").DataType("string")).
@@ -63,6 +66,7 @@ func (c *userWebService) GetWebService() *restful.WebService {
ws.Route(ws.POST("/").To(c.createUser).
Doc("create a user").
Filter(c.rbacUsecase.CheckPerm("user", "create")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(apis.CreateUserRequest{}).
Returns(200, "OK", apis.UserBase{}).
@@ -72,6 +76,7 @@ func (c *userWebService) GetWebService() *restful.WebService {
ws.Route(ws.GET("/{username}").To(c.detailUser).
Doc("get user detail").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("user", "detail")).
Filter(c.userCheckFilter).
Returns(200, "OK", apis.DetailUserResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
@@ -80,6 +85,7 @@ func (c *userWebService) GetWebService() *restful.WebService {
ws.Route(ws.PUT("/{username}").To(c.updateUser).
Doc("update a user's alias or password").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("user", "update")).
Filter(c.userCheckFilter).
Returns(200, "OK", apis.UserBase{}).
Returns(400, "Bad Request", bcode.Bcode{}).
@@ -88,6 +94,7 @@ func (c *userWebService) GetWebService() *restful.WebService {
ws.Route(ws.DELETE("/{username}").To(c.deleteUser).
Doc("delete a user").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("user", "delete")).
Returns(200, "OK", apis.EmptyResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
Writes(apis.EmptyResponse{}))
@@ -95,6 +102,7 @@ func (c *userWebService) GetWebService() *restful.WebService {
ws.Route(ws.GET("/{username}/disable").To(c.disableUser).
Doc("disable a user").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("user", "disable")).
Filter(c.userCheckFilter).
Returns(200, "OK", apis.EmptyResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
@@ -103,6 +111,7 @@ func (c *userWebService) GetWebService() *restful.WebService {
ws.Route(ws.GET("/{username}/enable").To(c.enableUser).
Doc("enable a user").
Metadata(restfulspec.KeyOpenAPITags, tags).
Filter(c.rbacUsecase.CheckPerm("user", "enable")).
Filter(c.userCheckFilter).
Returns(200, "OK", apis.EmptyResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
+5 -1
View File
@@ -27,12 +27,14 @@ import (
type velaQLWebService struct {
velaQLUsecase usecase.VelaQLUsecase
rbacUsecase usecase.RBACUsecase
}
// NewVelaQLWebService new velaQL webservice
func NewVelaQLWebService(velaQLUsecase usecase.VelaQLUsecase) WebService {
func NewVelaQLWebService(velaQLUsecase usecase.VelaQLUsecase, rbacUsecase usecase.RBACUsecase) WebService {
return &velaQLWebService{
velaQLUsecase: velaQLUsecase,
rbacUsecase: rbacUsecase,
}
}
@@ -48,6 +50,8 @@ func (v *velaQLWebService) GetWebService() *restful.WebService {
ws.Route(ws.GET("/").To(v.queryView).
Doc("use velaQL to query resource status").
Metadata(restfulspec.KeyOpenAPITags, tags).
// TODO: VelaQL is an open data query API that is currently not compatible with RBAC.
// Filter(v.rbacUsecase.CheckPerm("application", "detail")).
Param(ws.QueryParameter("velaql", "velaql query statement").DataType("string")).
Returns(200, "OK", apis.VelaQLViewResponse{}).
Returns(400, "Bad Request", bcode.Bcode{}).
+45 -23
View File
@@ -17,6 +17,8 @@ limitations under the License.
package webservice
import (
"context"
"log"
"net/http"
"time"
@@ -37,7 +39,7 @@ type WebService interface {
var registeredWebService []WebService
// RegisterWebService regist webservice
// RegisterWebService register webservice
func RegisterWebService(ws WebService) {
registeredWebService = append(registeredWebService, ws)
}
@@ -59,12 +61,13 @@ func returns500(b *restful.RouteBuilder) {
// Init init all webservice, pass in the required parameter object.
// It can be implemented using the idea of dependency injection.
func Init(ds datastore.DataStore, addonCacheTime time.Duration) {
func Init(ctx context.Context, ds datastore.DataStore, addonCacheTime time.Duration, initDatabase bool) map[string]interface{} {
clusterUsecase := usecase.NewClusterUsecase(ds)
envUsecase := usecase.NewEnvUsecase(ds)
workflowUsecase := usecase.NewWorkflowUsecase(ds, envUsecase)
projectUsecase := usecase.NewProjectUsecase(ds)
rbacUsecase := usecase.NewRBACUsecase(ds)
projectUsecase := usecase.NewProjectUsecase(ds, rbacUsecase)
envUsecase := usecase.NewEnvUsecase(ds, projectUsecase)
targetUsecase := usecase.NewTargetUsecase(ds)
workflowUsecase := usecase.NewWorkflowUsecase(ds, envUsecase)
oamApplicationUsecase := usecase.NewOAMApplicationUsecase()
velaQLUsecase := usecase.NewVelaQLUsecase()
definitionUsecase := usecase.NewDefinitionUsecase()
@@ -74,36 +77,55 @@ func Init(ds datastore.DataStore, addonCacheTime time.Duration) {
webhookUsecase := usecase.NewWebhookUsecase(ds, applicationUsecase)
systemInfoUsecase := usecase.NewSystemInfoUsecase(ds)
helmUsecase := usecase.NewHelmUsecase()
userUsecase := usecase.NewUserUsecase(ds, projectUsecase, systemInfoUsecase)
userUsecase := usecase.NewUserUsecase(ds, projectUsecase, systemInfoUsecase, rbacUsecase)
authenticationUsecase := usecase.NewAuthenticationUsecase(ds, systemInfoUsecase, userUsecase)
// init for default values
// Modules that require default data initialization, Call it here in order
if initDatabase {
initData(ctx, userUsecase, rbacUsecase, projectUsecase, targetUsecase)
}
// Application
RegisterWebService(NewApplicationWebService(applicationUsecase, envBindingUsecase, workflowUsecase))
RegisterWebService(NewProjectWebService(projectUsecase))
RegisterWebService(NewEnvWebService(envUsecase, applicationUsecase))
RegisterWebService(NewApplicationWebService(applicationUsecase, envBindingUsecase, workflowUsecase, rbacUsecase))
RegisterWebService(NewProjectWebService(projectUsecase, rbacUsecase, targetUsecase))
RegisterWebService(NewEnvWebService(envUsecase, applicationUsecase, rbacUsecase))
// Extension
RegisterWebService(NewDefinitionWebservice(definitionUsecase))
RegisterWebService(NewAddonWebService(addonUsecase))
RegisterWebService(NewEnabledAddonWebService(addonUsecase))
RegisterWebService(NewAddonRegistryWebService(addonUsecase))
RegisterWebService(NewDefinitionWebservice(definitionUsecase, rbacUsecase))
RegisterWebService(NewAddonWebService(addonUsecase, rbacUsecase))
RegisterWebService(NewEnabledAddonWebService(addonUsecase, rbacUsecase))
RegisterWebService(NewAddonRegistryWebService(addonUsecase, rbacUsecase))
// Resources
RegisterWebService(NewClusterWebService(clusterUsecase))
RegisterWebService(NewOAMApplication(oamApplicationUsecase))
RegisterWebService(NewClusterWebService(clusterUsecase, rbacUsecase))
RegisterWebService(NewOAMApplication(oamApplicationUsecase, rbacUsecase))
RegisterWebService(&policyDefinitionWebservice{})
RegisterWebService(&payloadTypesWebservice{})
RegisterWebService(NewTargetWebService(targetUsecase, applicationUsecase))
RegisterWebService(NewVelaQLWebService(velaQLUsecase))
RegisterWebService(NewTargetWebService(targetUsecase, applicationUsecase, rbacUsecase))
RegisterWebService(NewVelaQLWebService(velaQLUsecase, rbacUsecase))
RegisterWebService(NewWebhookWebService(webhookUsecase, applicationUsecase))
RegisterWebService(NewHelmWebService(helmUsecase))
// Authentication
RegisterWebService(NewAuthenticationWebService(authenticationUsecase))
RegisterWebService(NewUserWebService(userUsecase))
RegisterWebService(NewAuthenticationWebService(authenticationUsecase, userUsecase))
RegisterWebService(NewUserWebService(userUsecase, rbacUsecase))
RegisterWebService(NewSystemInfoWebService(systemInfoUsecase, rbacUsecase))
RegisterWebService(NewSystemInfoWebService(systemInfoUsecase))
// RBAC
RegisterWebService(NewRBACWebService(rbacUsecase))
RegisterWebService(NewHelmWebService(helmUsecase))
// return some usecase instance
return map[string]interface{}{"workflow": workflowUsecase, "project": projectUsecase}
}
// InitUsecase the usecase set that needs init data
type InitUsecase interface {
Init(ctx context.Context) error
}
func initData(ctx context.Context, inits ...InitUsecase) {
for _, init := range inits {
if err := init.Init(ctx); err != nil {
log.Fatalf("database init failure %s", err.Error())
}
}
}
+14 -138
View File
@@ -1,17 +1,17 @@
/*
Copyright 2022 The KubeVela Authors.
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sync
@@ -20,138 +20,14 @@ import (
"context"
"strconv"
"strings"
"time"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/apiserver/sync/convert"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/workflow/step"
)
// ConvertFromCRComponent concerts Application CR Component object into velaux data store component
func ConvertFromCRComponent(appPrimaryKey string, component common.ApplicationComponent) (model.ApplicationComponent, error) {
bc := model.ApplicationComponent{
AppPrimaryKey: appPrimaryKey,
Name: component.Name,
Type: component.Type,
ExternalRevision: component.ExternalRevision,
DependsOn: component.DependsOn,
Inputs: component.Inputs,
Outputs: component.Outputs,
Scopes: component.Scopes,
Creator: model.AutoGenComp,
}
if component.Properties != nil {
properties, err := model.NewJSONStruct(component.Properties)
if err != nil {
return bc, err
}
bc.Properties = properties
}
for _, trait := range component.Traits {
properties, err := model.NewJSONStruct(trait.Properties)
if err != nil {
return bc, err
}
bc.Traits = append(bc.Traits, model.ApplicationTrait{CreateTime: time.Now(), UpdateTime: time.Now(), Properties: properties, Type: trait.Type, Alias: trait.Type, Description: "auto gen"})
}
return bc, nil
}
// ConvertFromCRPolicy converts Application CR Policy object into velaux data store policy
func ConvertFromCRPolicy(appPrimaryKey string, policyCR v1beta1.AppPolicy, creator string) (model.ApplicationPolicy, error) {
plc := model.ApplicationPolicy{
AppPrimaryKey: appPrimaryKey,
Name: policyCR.Name,
Type: policyCR.Type,
Creator: creator,
}
if policyCR.Properties != nil {
properties, err := model.NewJSONStruct(policyCR.Properties)
if err != nil {
return plc, err
}
plc.Properties = properties
}
return plc, nil
}
// ConvertFromCRWorkflow converts Application CR Workflow section into velaux data store workflow
func ConvertFromCRWorkflow(ctx context.Context, cli client.Client, appPrimaryKey string, app *v1beta1.Application) (model.Workflow, []v1beta1.WorkflowStep, error) {
dataWf := model.Workflow{
AppPrimaryKey: appPrimaryKey,
// every namespace has a synced env
EnvName: model.AutoGenEnvNamePrefix + app.Namespace,
// every application has a synced workflow
Name: model.AutoGenWorkflowNamePrefix + appPrimaryKey,
Alias: model.AutoGenWorkflowNamePrefix + app.Name,
Description: model.AutoGenDesc,
}
if app.Spec.Workflow == nil {
return dataWf, nil, nil
}
var steps []v1beta1.WorkflowStep
if app.Spec.Workflow.Ref != "" {
dataWf.Name = app.Spec.Workflow.Ref
wf := &v1alpha1.Workflow{}
if err := cli.Get(ctx, types.NamespacedName{Namespace: app.GetNamespace(), Name: app.Spec.Workflow.Ref}, wf); err != nil {
return dataWf, nil, err
}
steps = step.ConvertSteps(wf.Steps)
} else {
steps = app.Spec.Workflow.Steps
}
for _, s := range steps {
if s.Properties == nil {
continue
}
properties, err := model.NewJSONStruct(s.Properties)
if err != nil {
return dataWf, nil, err
}
dataWf.Steps = append(dataWf.Steps, model.WorkflowStep{
Name: s.Name,
Type: s.Type,
Inputs: s.Inputs,
Outputs: s.Outputs,
DependsOn: s.DependsOn,
Properties: properties,
})
}
return dataWf, steps, nil
}
// ConvertFromCRTargets converts deployed Cluster/Namespace from Application CR Status into velaux data store
func ConvertFromCRTargets(targetApp *v1beta1.Application) []*model.Target {
var targets []*model.Target
nc := make(map[string]struct{})
for _, v := range targetApp.Status.AppliedResources {
var cluster = v.Cluster
if cluster == "" {
cluster = multicluster.ClusterLocalName
}
name := model.AutoGenTargetNamePrefix + cluster + "-" + v.Namespace
if _, ok := nc[name]; ok {
continue
}
nc[name] = struct{}{}
targets = append(targets, &model.Target{
Name: name,
Cluster: &model.ClusterTarget{
ClusterName: cluster,
Namespace: v.Namespace,
},
})
}
return targets
}
// ConvertApp2DatastoreApp will convert Application CR to datastore application related resources
func (c *CR2UX) ConvertApp2DatastoreApp(ctx context.Context, targetApp *v1beta1.Application) (*model.DataStoreApp, error) {
cli := c.cli
@@ -193,7 +69,7 @@ func (c *CR2UX) ConvertApp2DatastoreApp(ctx context.Context, targetApp *v1beta1.
// 2. convert component and trait
for _, cmp := range targetApp.Spec.Components {
compModel, err := ConvertFromCRComponent(appMeta.PrimaryKey(), cmp)
compModel, err := convert.FromCRComponent(appMeta.PrimaryKey(), cmp)
if err != nil {
return nil, err
}
@@ -201,7 +77,7 @@ func (c *CR2UX) ConvertApp2DatastoreApp(ctx context.Context, targetApp *v1beta1.
}
// 3. convert workflow
wf, steps, err := ConvertFromCRWorkflow(ctx, cli, appMeta.PrimaryKey(), targetApp)
wf, steps, err := convert.FromCRWorkflow(ctx, cli, appMeta.PrimaryKey(), targetApp)
if err != nil {
return nil, err
}
@@ -217,7 +93,7 @@ func (c *CR2UX) ConvertApp2DatastoreApp(ctx context.Context, targetApp *v1beta1.
return nil, err
}
for _, plc := range outsidePLC {
plcModel, err := ConvertFromCRPolicy(appMeta.PrimaryKey(), plc, model.AutoGenRefPolicy)
plcModel, err := convert.FromCRPolicy(appMeta.PrimaryKey(), plc, model.AutoGenRefPolicy)
if _, ok := innerPlc[plc.Name]; ok {
plcModel.Creator = model.AutoGenPolicy
}
+150
View File
@@ -0,0 +1,150 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package convert
import (
"context"
"time"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/workflow/step"
)
// FromCRComponent concerts Application CR Component object into velaux data store component
func FromCRComponent(appPrimaryKey string, component common.ApplicationComponent) (model.ApplicationComponent, error) {
bc := model.ApplicationComponent{
AppPrimaryKey: appPrimaryKey,
Name: component.Name,
Type: component.Type,
ExternalRevision: component.ExternalRevision,
DependsOn: component.DependsOn,
Inputs: component.Inputs,
Outputs: component.Outputs,
Scopes: component.Scopes,
Creator: model.AutoGenComp,
}
if component.Properties != nil {
properties, err := model.NewJSONStruct(component.Properties)
if err != nil {
return bc, err
}
bc.Properties = properties
}
for _, trait := range component.Traits {
properties, err := model.NewJSONStruct(trait.Properties)
if err != nil {
return bc, err
}
bc.Traits = append(bc.Traits, model.ApplicationTrait{CreateTime: time.Now(), UpdateTime: time.Now(), Properties: properties, Type: trait.Type, Alias: trait.Type, Description: "auto gen"})
}
return bc, nil
}
// FromCRPolicy converts Application CR Policy object into velaux data store policy
func FromCRPolicy(appPrimaryKey string, policyCR v1beta1.AppPolicy, creator string) (model.ApplicationPolicy, error) {
plc := model.ApplicationPolicy{
AppPrimaryKey: appPrimaryKey,
Name: policyCR.Name,
Type: policyCR.Type,
Creator: creator,
}
if policyCR.Properties != nil {
properties, err := model.NewJSONStruct(policyCR.Properties)
if err != nil {
return plc, err
}
plc.Properties = properties
}
return plc, nil
}
// FromCRWorkflow converts Application CR Workflow section into velaux data store workflow
func FromCRWorkflow(ctx context.Context, cli client.Client, appPrimaryKey string, app *v1beta1.Application) (model.Workflow, []v1beta1.WorkflowStep, error) {
dataWf := model.Workflow{
AppPrimaryKey: appPrimaryKey,
// every namespace has a synced env
EnvName: model.AutoGenEnvNamePrefix + app.Namespace,
// every application has a synced workflow
Name: model.AutoGenWorkflowNamePrefix + appPrimaryKey,
Alias: model.AutoGenWorkflowNamePrefix + app.Name,
Description: model.AutoGenDesc,
}
if app.Spec.Workflow == nil {
return dataWf, nil, nil
}
var steps []v1beta1.WorkflowStep
if app.Spec.Workflow.Ref != "" {
dataWf.Name = app.Spec.Workflow.Ref
wf := &v1alpha1.Workflow{}
if err := cli.Get(ctx, types.NamespacedName{Namespace: app.GetNamespace(), Name: app.Spec.Workflow.Ref}, wf); err != nil {
return dataWf, nil, err
}
steps = step.ConvertSteps(wf.Steps)
} else {
steps = app.Spec.Workflow.Steps
}
for _, s := range steps {
if s.Properties == nil {
continue
}
properties, err := model.NewJSONStruct(s.Properties)
if err != nil {
return dataWf, nil, err
}
dataWf.Steps = append(dataWf.Steps, model.WorkflowStep{
Name: s.Name,
Type: s.Type,
Inputs: s.Inputs,
Outputs: s.Outputs,
DependsOn: s.DependsOn,
Properties: properties,
})
}
return dataWf, steps, nil
}
// FromCRTargets converts deployed Cluster/Namespace from Application CR Status into velaux data store
func FromCRTargets(targetApp *v1beta1.Application) []*model.Target {
var targets []*model.Target
nc := make(map[string]struct{})
for _, v := range targetApp.Status.AppliedResources {
var cluster = v.Cluster
if cluster == "" {
cluster = multicluster.ClusterLocalName
}
name := model.AutoGenTargetNamePrefix + cluster + "-" + v.Namespace
if _, ok := nc[name]; ok {
continue
}
nc[name] = struct{}{}
targets = append(targets, &model.Target{
Name: name,
Cluster: &model.ClusterTarget{
ClusterName: cluster,
Namespace: v.Namespace,
},
})
}
return targets
}
+10 -5
View File
@@ -26,6 +26,7 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase"
"github.com/oam-dev/kubevela/pkg/oam"
)
@@ -79,9 +80,10 @@ func (c *CR2UX) getApp(ctx context.Context, name, namespace string) (*model.Appl
// CR2UX provides the Add/Update/Delete method
type CR2UX struct {
ds datastore.DataStore
cli client.Client
cache sync.Map
ds datastore.DataStore
cli client.Client
cache sync.Map
usecases map[string]interface{}
}
func formatAppComposedName(name, namespace string) string {
@@ -106,8 +108,11 @@ func (c *CR2UX) AddOrUpdate(ctx context.Context, targetApp *v1beta1.Application)
log.Logger.Errorf("Convert App to data store err %v", err)
return err
}
if err = StoreProject(ctx, dsApp.AppMeta.Project, ds); err != nil {
pu, ok := c.usecases["project"].(usecase.ProjectUsecase)
if !ok {
log.Logger.Warnf("not provide project usecase instance")
}
if err = StoreProject(ctx, dsApp.AppMeta.Project, ds, pu); err != nil {
log.Logger.Errorf("get or create project for sync process err %v", err)
return err
}
+11 -6
View File
@@ -24,11 +24,13 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
v1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase"
"github.com/oam-dev/kubevela/pkg/utils"
)
// StoreProject will create project for synced application
func StoreProject(ctx context.Context, name string, ds datastore.DataStore) error {
func StoreProject(ctx context.Context, name string, ds datastore.DataStore, projectUsecase usecase.ProjectUsecase) error {
err := ds.Get(ctx, &model.Project{Name: name})
if err == nil {
// it means the record already exists, don't need to add anything
@@ -38,12 +40,15 @@ func StoreProject(ctx context.Context, name string, ds datastore.DataStore) erro
// other database error, return it
return err
}
proj := &model.Project{
Name: name,
Description: model.AutoGenProj,
Alias: strings.Title(name),
if projectUsecase != nil {
_, err := projectUsecase.CreateProject(ctx, v1.CreateProjectRequest{
Name: name,
Alias: strings.Title(name),
Owner: model.DefaultAdminUserName,
Description: model.AutoGenProj})
return err
}
return ds.Add(ctx, proj)
return nil
}
// StoreAppMeta will sync application metadata from CR to datastore
+10 -6
View File
@@ -37,7 +37,7 @@ import (
)
// Start prepares watchers and run their controllers, then waits for process termination signals
func Start(ctx context.Context, ds datastore.DataStore, cfg *rest.Config) {
func Start(ctx context.Context, ds datastore.DataStore, cfg *rest.Config, usecases map[string]interface{}) {
k8sClient, err := clients.GetKubeClient()
if err != nil {
logrus.Fatal(err)
@@ -50,10 +50,10 @@ func Start(ctx context.Context, ds datastore.DataStore, cfg *rest.Config) {
f := dynamicinformer.NewFilteredDynamicSharedInformerFactory(dynamicClient, 0, v1.NamespaceAll, nil)
startAppSyncing(ctx, f, ds, k8sClient)
startAppSyncing(ctx, f, ds, k8sClient, usecases)
}
func startAppSyncing(ctx context.Context, factory dynamicinformer.DynamicSharedInformerFactory, ds datastore.DataStore, cli client.Client) {
func startAppSyncing(ctx context.Context, factory dynamicinformer.DynamicSharedInformerFactory, ds datastore.DataStore, cli client.Client, usecases map[string]interface{}) {
var err error
informer := factory.ForResource(v1beta1.SchemeGroupVersion.WithResource("applications")).Informer()
getApp := func(obj interface{}) *v1beta1.Application {
@@ -62,10 +62,14 @@ func startAppSyncing(ctx context.Context, factory dynamicinformer.DynamicSharedI
_ = json.Unmarshal(bs, app)
return app
}
if usecases == nil {
usecases = make(map[string]interface{})
}
cu := &CR2UX{
ds: ds,
cli: cli,
cache: sync.Map{},
ds: ds,
cli: cli,
cache: sync.Map{},
usecases: usecases,
}
if err = cu.initCache(ctx); err != nil {
klog.Fatal("sync app init err", err)
+1 -1
View File
@@ -61,7 +61,7 @@ var _ = Describe("Test Worker CR sync to datastore", func() {
By("Start syncing")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go Start(ctx, ds, cfg)
go Start(ctx, ds, cfg, nil)
By("create test app1 and check the syncing results")
app1 := &v1beta1.Application{}