Skip to content

Commit

Permalink
Feat: add the API that rollbacks the application (kubevela#5273)
Browse files Browse the repository at this point in the history
* Feat: add the API that rollbacks the application

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

* Fix: enhance the test cases

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

* Fix: use the klog/v2 package

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

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
  • Loading branch information
barnettZQG authored and callmeoldprince committed Jan 6, 2023
1 parent efcaa01 commit df69f1b
Show file tree
Hide file tree
Showing 20 changed files with 663 additions and 209 deletions.
207 changes: 132 additions & 75 deletions docs/apidoc/swagger.json

Large diffs are not rendered by default.

128 changes: 98 additions & 30 deletions pkg/apiserver/domain/service/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import (
velatypes "github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
"github.com/oam-dev/kubevela/pkg/apiserver/domain/repository"
syncconvert "github.com/oam-dev/kubevela/pkg/apiserver/event/sync/convert"
"github.com/oam-dev/kubevela/pkg/apiserver/event/sync/convert"
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
assembler "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/assembler/v1"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
Expand All @@ -51,6 +51,7 @@ import (
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
pkgUtils "github.com/oam-dev/kubevela/pkg/utils"
"github.com/oam-dev/kubevela/pkg/utils/app"
"github.com/oam-dev/kubevela/pkg/utils/apply"
commonutil "github.com/oam-dev/kubevela/pkg/utils/common"
)
Expand Down Expand Up @@ -89,6 +90,7 @@ type ApplicationService interface {
UpdateApplicationTrait(ctx context.Context, app *model.Application, component *model.ApplicationComponent, traitType string, req apisv1.UpdateApplicationTraitRequest) (*apisv1.ApplicationTrait, error)
ListRevisions(ctx context.Context, appName, envName, status string, page, pageSize int) (*apisv1.ListRevisionsResponse, error)
DetailRevision(ctx context.Context, appName, revisionName string) (*apisv1.DetailRevisionResponse, error)
RollbackWithRevision(ctx context.Context, app *model.Application, revisionName string) (*apisv1.ApplicationRollbackResponse, error)
Statistics(ctx context.Context, app *model.Application) (*apisv1.ApplicationStatisticsResponse, error)
ListRecords(ctx context.Context, appName string) (*apisv1.ListWorkflowRecordsResponse, error)
CompareApp(ctx context.Context, app *model.Application, compareReq apisv1.AppCompareReq) (*apisv1.AppCompareResponse, error)
Expand Down Expand Up @@ -698,16 +700,17 @@ func (c *applicationServiceImpl) Deploy(ctx context.Context, app *model.Applicat
status = revision.Status
}
if status != model.RevisionStatusComplete && status != model.RevisionStatusTerminated && status != model.RevisionStatusFail {
klog.Warningf("last app revision can not complete %s/%s", list[0].(*model.ApplicationRevision).AppPrimaryKey, list[0].(*model.ApplicationRevision).Version)
klog.Warningf("last app revision can not complete %s/%s,the current status is %s", list[0].(*model.ApplicationRevision).AppPrimaryKey, list[0].(*model.ApplicationRevision).Version, status)
return nil, bcode.ErrDeployConflict
}
}
}

var appRevision = &model.ApplicationRevision{
AppPrimaryKey: app.PrimaryKey(),
Version: version,
RevisionCRName: version,
AppPrimaryKey: app.PrimaryKey(),
Version: version,
// Setting it when syncing the workflow status
RevisionCRName: "",
ApplyAppConfig: string(configByte),
Status: model.RevisionStatusInit,
DeployUser: userName,
Expand Down Expand Up @@ -1425,13 +1428,29 @@ func (c *applicationServiceImpl) CompareApp(ctx context.Context, appModel *model
var base, compareTarget *v1beta1.Application
var err error
var envNameByRevision string
switch {
case compareReq.CompareLatestWithRunning != nil:
base, err = c.renderOAMApplication(ctx, appModel, "", compareReq.CompareLatestWithRunning.Env, "")

getRunningApp := func() *v1beta1.Application {
var envName string
if compareReq.CompareLatestWithRunning != nil {
envName = compareReq.CompareLatestWithRunning.Env
}
if compareReq.CompareRevisionWithRunning != nil {
envName = envNameByRevision
}
if envName == "" {
return nil
}
app, err := c.GetApplicationCRInEnv(ctx, appModel, envName)
if err != nil {
klog.Errorf("failed to build the latest application %s", err.Error())
break
klog.Errorf("failed to query the application CR %s", err.Error())
return nil
}
return app
}
switch {
case compareReq.CompareLatestWithRunning != nil:
base = getRunningApp()

case compareReq.CompareRevisionWithRunning != nil || compareReq.CompareRevisionWithLatest != nil:
var revision = ""
if compareReq.CompareRevisionWithRunning != nil {
Expand All @@ -1448,23 +1467,9 @@ func (c *applicationServiceImpl) CompareApp(ctx context.Context, appModel *model
}

switch {
case compareReq.CompareLatestWithRunning != nil || compareReq.CompareRevisionWithRunning != nil:
var envName string
if compareReq.CompareLatestWithRunning != nil {
envName = compareReq.CompareLatestWithRunning.Env
}
if compareReq.CompareRevisionWithRunning != nil {
envName = envNameByRevision
}
if envName == "" {
break
}
compareTarget, err = c.GetApplicationCRInEnv(ctx, appModel, envName)
if err != nil {
klog.Errorf("failed to query the application CR %s", err.Error())
break
}
case compareReq.CompareRevisionWithLatest != nil:
case compareReq.CompareRevisionWithRunning != nil:
compareTarget = getRunningApp()
case compareReq.CompareRevisionWithLatest != nil || compareReq.CompareLatestWithRunning != nil:
compareTarget, err = c.renderOAMApplication(ctx, appModel, "", envNameByRevision, "")
if err != nil {
klog.Errorf("failed to build the latest application %s", err.Error())
Expand Down Expand Up @@ -1625,7 +1630,7 @@ func (c *applicationServiceImpl) 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 := syncconvert.FromCRComponent(appPrimaryKey, comp)
compModel, err := convert.FromCRComponent(appPrimaryKey, comp)
if err != nil {
return &apisv1.AppResetResponse{}, bcode.ErrInvalidProperties
}
Expand All @@ -1650,6 +1655,71 @@ func (c *applicationServiceImpl) resetApp(ctx context.Context, targetApp *v1beta
return &apisv1.AppResetResponse{IsReset: true}, nil
}

func (c *applicationServiceImpl) RollbackWithRevision(ctx context.Context, application *model.Application, revisionVersion string) (*apisv1.ApplicationRollbackResponse, error) {
revision, err := c.DetailRevision(ctx, application.Name, revisionVersion)
if err != nil {
return nil, err
}
appCR, err := c.GetApplicationCRInEnv(ctx, application, revision.EnvName)
if err != nil {
return nil, err
}
var publishVersion = utils.GenerateVersion(revision.WorkflowName)
noRevision := false
var rollbackApplication *v1beta1.Application
if appCR != nil {
// The RevisionCRName is incorrect in the old version, ignore it.
if revision.RevisionCRName == revision.Version || revision.RevisionCRName == "" {
noRevision = true
} else {
_, appCR, err := app.RollbackApplicationWithRevision(ctx, c.KubeClient, appCR.Name, appCR.Namespace, revision.RevisionCRName, publishVersion)
if err != nil {
switch {
case errors.Is(err, app.ErrNotMatchRevision):
noRevision = true
case errors.Is(err, app.ErrRevisionNotChange):
return nil, bcode.ErrApplicationRevisionConflict
default:
return nil, err
}
}
rollbackApplication = appCR
}
}

// Rollback by the local revision
if appCR == nil || noRevision {
rollBackApp := &v1beta1.Application{}
if err := yaml.Unmarshal([]byte(revision.ApplyAppConfig), rollBackApp); err != nil {
return nil, err
}
oam.SetPublishVersion(rollBackApp, publishVersion)
if appCR != nil {
rollBackApp.ResourceVersion = appCR.ResourceVersion
} else {
rollBackApp.ResourceVersion = ""
}
err = c.Apply.Apply(ctx, rollBackApp)
if err != nil {
klog.Errorf("rollback the app %s failure %s", application.PrimaryKey(), err.Error())
return nil, err
}
rollbackApplication = rollBackApp
}

work, _, err := convert.FromCRWorkflow(ctx, c.KubeClient, application.PrimaryKey(), rollbackApplication)
if err != nil {
return nil, err
}
record, err := c.WorkflowService.CreateWorkflowRecord(ctx, application, rollbackApplication, &work)
if err != nil {
return nil, fmt.Errorf("create workflow record failure %w", err)
}
return &apisv1.ApplicationRollbackResponse{
WorkflowRecord: assembler.ConvertFromRecordModel(record).WorkflowRecordBase,
}, nil
}

func dryRunApplication(ctx context.Context, c commonutil.Args, app *v1beta1.Application) (bytes.Buffer, error) {
var buff = bytes.Buffer{}
if _, err := fmt.Fprintf(&buff, "---\n# Application(%s) \n---\n\n", app.Name); err != nil {
Expand Down Expand Up @@ -1693,9 +1763,7 @@ func dryRunApplication(ctx context.Context, c commonutil.Args, app *v1beta1.Appl
// ignore the workflow spec
func ignoreSomeParams(o *v1beta1.Application) {
var defaultApplication = v1beta1.Application{}
// only compare the spec without the workflow
defaultApplication.Spec = o.Spec
defaultApplication.Spec.Workflow = nil
defaultApplication.Name = o.Name
defaultApplication.Namespace = o.Namespace

Expand Down
6 changes: 4 additions & 2 deletions pkg/apiserver/domain/service/application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,8 +532,10 @@ var _ = Describe("Test application service function", func() {
})
Expect(err).Should(BeNil())
Expect(cmp.Diff(compareResponse.IsDiff, true)).Should(BeEmpty())
Expect(cmp.Diff(compareResponse.TargetAppYAML, "")).Should(BeEmpty())
Expect(cmp.Diff(compareResponse.BaseAppYAML, "")).ShouldNot(BeEmpty())
// The target represents the latest config
Expect(cmp.Diff(compareResponse.TargetAppYAML, "")).ShouldNot(BeEmpty())
// The base represents the running config
Expect(cmp.Diff(compareResponse.BaseAppYAML, "")).Should(BeEmpty())

By("compare when app's env add target, should return false")
_, err = targetService.CreateTarget(context.TODO(), v1.CreateTargetRequest{Name: "dev-target1", Project: appModel.Project, Cluster: &v1.ClusterTarget{ClusterName: "local", Namespace: "dev-target1"}})
Expand Down
17 changes: 8 additions & 9 deletions pkg/apiserver/domain/service/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,14 @@ import (
"sort"

"github.com/pkg/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/oam-dev/kubevela/apis/types"
apis "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
"github.com/oam-dev/kubevela/pkg/config"
"github.com/oam-dev/kubevela/pkg/utils"
"github.com/oam-dev/kubevela/pkg/utils/apply"
)

Expand Down Expand Up @@ -62,7 +61,8 @@ type configServiceImpl struct {

// ListTemplates list the config templates
func (u *configServiceImpl) ListTemplates(ctx context.Context, project, scope string) ([]*apis.ConfigTemplate, error) {
queryTemplates, err := u.Factory.ListTemplates(ctx, types.DefaultKubeVelaNS, scope)
listCtx := utils.WithProject(ctx, "")
queryTemplates, err := u.Factory.ListTemplates(listCtx, types.DefaultKubeVelaNS, scope)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -100,7 +100,8 @@ func (u *configServiceImpl) GetTemplate(ctx context.Context, tem config.Namespac
if tem.Namespace == "" {
tem.Namespace = types.DefaultKubeVelaNS
}
template, err := u.Factory.LoadTemplate(ctx, tem.Name, tem.Namespace)
getCtx := utils.WithProject(ctx, "")
template, err := u.Factory.LoadTemplate(getCtx, tem.Name, tem.Namespace)
if err != nil {
if errors.Is(err, config.ErrTemplateNotFound) {
return nil, bcode.ErrTemplateNotFound
Expand Down Expand Up @@ -133,9 +134,6 @@ func (u *configServiceImpl) CreateConfig(ctx context.Context, project string, re
return nil, err
}
ns = pro.GetNamespace()
if err := utils.CreateNamespace(ctx, u.KubeClient, ns); err != nil && !apierrors.IsAlreadyExists(err) {
return nil, err
}
}
var properties = make(map[string]interface{})
if err := json.Unmarshal([]byte(req.Properties), &properties); err != nil {
Expand Down Expand Up @@ -210,6 +208,7 @@ func (u *configServiceImpl) ListConfigs(ctx context.Context, project string, tem
var list []*apis.Config
scope := ""
var projectNamespace string
listCtx := utils.WithProject(ctx, "")
if project != "" {
scope = "project"
pro, err := u.ProjectService.GetProject(ctx, project)
Expand All @@ -218,7 +217,7 @@ func (u *configServiceImpl) ListConfigs(ctx context.Context, project string, tem
}
projectNamespace = pro.GetNamespace()
// query the configs belong to the project scope from the system namespace
configs, err := u.Factory.ListConfigs(ctx, pro.GetNamespace(), template, "", true)
configs, err := u.Factory.ListConfigs(listCtx, pro.GetNamespace(), template, "", true)
if err != nil {
return nil, err
}
Expand All @@ -227,7 +226,7 @@ func (u *configServiceImpl) ListConfigs(ctx context.Context, project string, tem
}
}

configs, err := u.Factory.ListConfigs(ctx, types.DefaultKubeVelaNS, template, scope, true)
configs, err := u.Factory.ListConfigs(listCtx, types.DefaultKubeVelaNS, template, scope, true)
if err != nil {
return nil, err
}
Expand Down
33 changes: 31 additions & 2 deletions pkg/apiserver/domain/service/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package service

import (
"bytes"
"context"
"errors"
"fmt"
Expand All @@ -33,6 +34,7 @@ import (
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
apiutils "github.com/oam-dev/kubevela/pkg/apiserver/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
"github.com/oam-dev/kubevela/pkg/auth"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/utils"
)
Expand Down Expand Up @@ -292,6 +294,10 @@ func (p *projectServiceImpl) DeleteProject(ctx context.Context, name string) err
if err := p.Store.Delete(ctx, &model.Project{Name: name}); err != nil {
return err
}

if err := managePrivilegesForProject(ctx, p.K8sClient, &model.Project{Name: name}, true); err != nil {
return err
}
return nil
}

Expand Down Expand Up @@ -327,7 +333,6 @@ func (p *projectServiceImpl) CreateProject(ctx context.Context, req apisv1.Creat
if err := utils.CreateNamespace(createCtx, p.K8sClient, namespace); err != nil && !apierrors.IsAlreadyExists(err) {
return nil, bcode.ErrProjectNamespaceFail
}

newProject := &model.Project{
Name: req.Name,
Description: req.Description,
Expand All @@ -340,13 +345,33 @@ func (p *projectServiceImpl) CreateProject(ctx context.Context, req apisv1.Creat
return nil, err
}

if err := managePrivilegesForProject(createCtx, p.K8sClient, newProject, false); err != nil {
return nil, err
}

if err := p.RbacService.SyncDefaultRoleAndUsersForProject(ctx, newProject); err != nil {
klog.Errorf("fail to sync the default role and users for the project: %s", err.Error())
}

return ConvertProjectModel2Base(newProject, user), nil
}

// managePrivilegesForProject grant or revoke privileges for project
func managePrivilegesForProject(ctx context.Context, cli client.Client, project *model.Project, revoke bool) error {
p := &auth.ApplicationPrivilege{Cluster: types.ClusterLocalName, Namespace: project.Namespace}
identity := &auth.Identity{Groups: []string{apiutils.KubeVelaProjectGroupPrefix + project.Name}}
writer := &bytes.Buffer{}
f, msg := auth.GrantPrivileges, "GrantPrivileges"
if revoke {
f, msg = auth.RevokePrivileges, "RevokePrivileges"
}
if err := f(ctx, cli, []auth.PrivilegeDescription{p}, identity, writer); err != nil {
return err
}
klog.Infof("%s: %s", msg, writer.String())
return nil
}

// UpdateProject update project
func (p *projectServiceImpl) UpdateProject(ctx context.Context, projectName string, req apisv1.UpdateProjectRequest) (*apisv1.ProjectBase, error) {
project, err := p.GetProject(ctx, projectName)
Expand Down Expand Up @@ -375,6 +400,9 @@ func (p *projectServiceImpl) UpdateProject(ctx context.Context, projectName stri
if err != nil {
return nil, err
}
if err := managePrivilegesForProject(ctx, p.K8sClient, project, false); err != nil {
return nil, err
}
return ConvertProjectModel2Base(project, user), nil
}

Expand Down Expand Up @@ -512,7 +540,8 @@ func (p *projectServiceImpl) UpdateProjectUser(ctx context.Context, projectName

func (p *projectServiceImpl) ListTerraformProviders(ctx context.Context, projectName string) ([]*apisv1.TerraformProvider, error) {
l := &terraformapi.ProviderList{}
if err := p.K8sClient.List(ctx, l, client.InNamespace(types.ProviderNamespace)); err != nil {
listCtx := apiutils.WithProject(ctx, "")
if err := p.K8sClient.List(listCtx, l, client.InNamespace(types.ProviderNamespace)); err != nil {
if meta.IsNoMatchError(err) {
return []*apisv1.TerraformProvider{}, nil
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/apiserver/domain/service/rbac.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ var defaultProjectPermissionTemplate = []*model.PermissionTemplate{
Name: "pipeline-management",
Alias: "Pipeline Management",
Resources: []string{
"project:{projectName}/pipeline:*",
"project:{projectName}/pipeline:*/*",
},
Actions: []string{"*"},
Effect: "Allow",
Expand Down
Loading

0 comments on commit df69f1b

Please sign in to comment.