Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: add the API that rollbacks the application #5273

Merged
merged 3 commits into from
Jan 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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