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

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
}

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
}

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
}

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

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)

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{}