mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-26 03:41:01 +00:00
Update pipeline state on server as a whole on approval (#3504)
We can not just update some records for steps, as we want the pipeline engine as single source of truth but not manage the state. And the server should only manage the state but not how pipelines work. We can match the pipeline but neither workflows or steps 1:1, so we "update" them as a whole by deleting existing workflow and step data and insert the new info from engine. close #3494 close #3472 --------- *Sponsored by Kithara Software GmbH* --------- Co-authored-by: Robert Kaussow <xoxys@rknet.org>
This commit is contained in:
parent
43264a5f8e
commit
e57a09a404
8 changed files with 78 additions and 230 deletions
|
@ -1,4 +1,4 @@
|
|||
// Code generated by mockery v2.40.3. DO NOT EDIT.
|
||||
// Code generated by mockery v2.42.1. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
|
|
|
@ -79,25 +79,11 @@ func Approve(ctx context.Context, store store.Store, currentPipeline *model.Pipe
|
|||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
// TODO improve this
|
||||
for _, item := range pipelineItems {
|
||||
for _, wf := range currentPipeline.Workflows {
|
||||
if item.Workflow.Name == wf.Name {
|
||||
item.Workflow = wf
|
||||
for _, stage := range item.Config.Stages {
|
||||
for _, step := range stage.Steps {
|
||||
for _, storeStep := range wf.Children {
|
||||
if storeStep.Name == step.Name {
|
||||
step.UUID = storeStep.UUID
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
// we have no way to link old workflows and steps in database to new engine generated steps,
|
||||
// so we just delete the old and insert the new ones
|
||||
if err := store.WorkflowsReplace(currentPipeline, currentPipeline.Workflows); err != nil {
|
||||
log.Error().Err(err).Str("repo", repo.FullName).Msgf("error persisting new steps for %s#%d after approval", repo.FullName, currentPipeline.Number)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
publishPipeline(ctx, currentPipeline, repo, user)
|
||||
|
|
|
@ -125,6 +125,9 @@ func setPipelineStepsOnPipeline(pipeline *model.Pipeline, pipelineItems []*stepb
|
|||
}
|
||||
}
|
||||
|
||||
// the workflows in the pipeline should be empty as only we do populate them,
|
||||
// but if a pipeline was already loaded form database it might contain things, so we just clean it
|
||||
pipeline.Workflows = nil
|
||||
for _, item := range pipelineItems {
|
||||
for _, stage := range item.Config.Stages {
|
||||
for _, step := range stage.Steps {
|
||||
|
|
|
@ -126,24 +126,7 @@ func (s storage) DeletePipeline(pipeline *model.Pipeline) error {
|
|||
}
|
||||
|
||||
func (s storage) deletePipeline(sess *xorm.Session, pipelineID int64) error {
|
||||
// delete related steps
|
||||
for startSteps := 0; ; startSteps += perPage {
|
||||
stepIDs := make([]int64, 0, perPage)
|
||||
if err := sess.Limit(perPage, startSteps).Table("steps").Cols("step_id").Where("step_pipeline_id = ?", pipelineID).Find(&stepIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(stepIDs) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for i := range stepIDs {
|
||||
if err := deleteStep(sess, stepIDs[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := sess.Where("workflow_pipeline_id = ?", pipelineID).Delete(new(model.Workflow)); err != nil {
|
||||
if err := s.workflowsDelete(sess, pipelineID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,14 @@ func (s storage) WorkflowsCreate(workflows []*model.Workflow) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := s.workflowsCreate(sess, workflows); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func (s storage) workflowsCreate(sess *xorm.Session, workflows []*model.Workflow) error {
|
||||
for i := range workflows {
|
||||
// only Insert on single object ref set auto created ID back to object
|
||||
if err := s.stepCreate(sess, workflows[i].Children); err != nil {
|
||||
|
@ -53,10 +61,50 @@ func (s storage) WorkflowsCreate(workflows []*model.Workflow) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WorkflowsReplace performs an atomic replacement of workflows and associated steps by deleting all existing workflows and steps and inserting the new ones
|
||||
func (s storage) WorkflowsReplace(pipeline *model.Pipeline, workflows []*model.Workflow) error {
|
||||
sess := s.engine.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.workflowsDelete(sess, pipeline.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.workflowsCreate(sess, workflows); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func (s storage) workflowsDelete(sess *xorm.Session, pipelineID int64) error {
|
||||
// delete related steps
|
||||
for startSteps := 0; ; startSteps += perPage {
|
||||
stepIDs := make([]int64, 0, perPage)
|
||||
if err := sess.Limit(perPage, startSteps).Table("steps").Cols("step_id").Where("step_pipeline_id = ?", pipelineID).Find(&stepIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(stepIDs) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for i := range stepIDs {
|
||||
if err := deleteStep(sess, stepIDs[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err := sess.Where("workflow_pipeline_id = ?", pipelineID).Delete(new(model.Workflow))
|
||||
return err
|
||||
}
|
||||
|
||||
func (s storage) WorkflowList(pipeline *model.Pipeline) ([]*model.Workflow, error) {
|
||||
return s.workflowList(s.engine.NewSession(), pipeline)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Code generated by mockery v2.40.3. DO NOT EDIT.
|
||||
// Code generated by mockery v2.42.1. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
|
@ -174,52 +174,6 @@ func (_m *Store) Close() error {
|
|||
return r0
|
||||
}
|
||||
|
||||
// ConfigCreate provides a mock function with given fields: _a0
|
||||
func (_m *Store) ConfigCreate(_a0 *model.Config) error {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ConfigCreate")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.Config) error); ok {
|
||||
r0 = rf(_a0)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ConfigFindApproved provides a mock function with given fields: _a0
|
||||
func (_m *Store) ConfigFindApproved(_a0 *model.Config) (bool, error) {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ConfigFindApproved")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*model.Config) (bool, error)); ok {
|
||||
return rf(_a0)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*model.Config) bool); ok {
|
||||
r0 = rf(_a0)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*model.Config) error); ok {
|
||||
r1 = rf(_a0)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ConfigPersist provides a mock function with given fields: _a0
|
||||
func (_m *Store) ConfigPersist(_a0 *model.Config) (*model.Config, error) {
|
||||
ret := _m.Called(_a0)
|
||||
|
@ -645,36 +599,6 @@ func (_m *Store) GetPipeline(_a0 int64) (*model.Pipeline, error) {
|
|||
return r0, r1
|
||||
}
|
||||
|
||||
// GetPipelineCommit provides a mock function with given fields: _a0, _a1, _a2
|
||||
func (_m *Store) GetPipelineCommit(_a0 *model.Repo, _a1 string, _a2 string) (*model.Pipeline, error) {
|
||||
ret := _m.Called(_a0, _a1, _a2)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetPipelineCommit")
|
||||
}
|
||||
|
||||
var r0 *model.Pipeline
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*model.Repo, string, string) (*model.Pipeline, error)); ok {
|
||||
return rf(_a0, _a1, _a2)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*model.Repo, string, string) *model.Pipeline); ok {
|
||||
r0 = rf(_a0, _a1, _a2)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Pipeline)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*model.Repo, string, string) error); ok {
|
||||
r1 = rf(_a0, _a1, _a2)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetPipelineCount provides a mock function with given fields:
|
||||
func (_m *Store) GetPipelineCount() (int64, error) {
|
||||
ret := _m.Called()
|
||||
|
@ -853,66 +777,6 @@ func (_m *Store) GetPipelineQueue() ([]*model.Feed, error) {
|
|||
return r0, r1
|
||||
}
|
||||
|
||||
// GetPipelineRef provides a mock function with given fields: _a0, _a1
|
||||
func (_m *Store) GetPipelineRef(_a0 *model.Repo, _a1 string) (*model.Pipeline, error) {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetPipelineRef")
|
||||
}
|
||||
|
||||
var r0 *model.Pipeline
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(*model.Repo, string) (*model.Pipeline, error)); ok {
|
||||
return rf(_a0, _a1)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(*model.Repo, string) *model.Pipeline); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Pipeline)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(*model.Repo, string) error); ok {
|
||||
r1 = rf(_a0, _a1)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetRedirection provides a mock function with given fields: _a0
|
||||
func (_m *Store) GetRedirection(_a0 string) (*model.Redirection, error) {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetRedirection")
|
||||
}
|
||||
|
||||
var r0 *model.Redirection
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string) (*model.Redirection, error)); ok {
|
||||
return rf(_a0)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string) *model.Redirection); ok {
|
||||
r0 = rf(_a0)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Redirection)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(_a0)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetRepo provides a mock function with given fields: _a0
|
||||
func (_m *Store) GetRepo(_a0 int64) (*model.Repo, error) {
|
||||
ret := _m.Called(_a0)
|
||||
|
@ -1363,24 +1227,6 @@ func (_m *Store) LogFind(_a0 *model.Step) ([]*model.LogEntry, error) {
|
|||
return r0, r1
|
||||
}
|
||||
|
||||
// LogSave provides a mock function with given fields: _a0, _a1
|
||||
func (_m *Store) LogSave(_a0 *model.Step, _a1 []*model.LogEntry) error {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for LogSave")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.Step, []*model.LogEntry) error); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Migrate provides a mock function with given fields: _a0
|
||||
func (_m *Store) Migrate(_a0 bool) error {
|
||||
ret := _m.Called(_a0)
|
||||
|
@ -1633,24 +1479,6 @@ func (_m *Store) OrgUpdate(_a0 *model.Org) error {
|
|||
return r0
|
||||
}
|
||||
|
||||
// PermDelete provides a mock function with given fields: perm
|
||||
func (_m *Store) PermDelete(perm *model.Perm) error {
|
||||
ret := _m.Called(perm)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PermDelete")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.Perm) error); ok {
|
||||
r0 = rf(perm)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// PermFind provides a mock function with given fields: user, repo
|
||||
func (_m *Store) PermFind(user *model.User, repo *model.Repo) (*model.Perm, error) {
|
||||
ret := _m.Called(user, repo)
|
||||
|
@ -1681,24 +1509,6 @@ func (_m *Store) PermFind(user *model.User, repo *model.Repo) (*model.Perm, erro
|
|||
return r0, r1
|
||||
}
|
||||
|
||||
// PermFlush provides a mock function with given fields: user, before
|
||||
func (_m *Store) PermFlush(user *model.User, before int64) error {
|
||||
ret := _m.Called(user, before)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PermFlush")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.User, int64) error); ok {
|
||||
r0 = rf(user, before)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// PermUpsert provides a mock function with given fields: perm
|
||||
func (_m *Store) PermUpsert(perm *model.Perm) error {
|
||||
ret := _m.Called(perm)
|
||||
|
@ -2609,6 +2419,24 @@ func (_m *Store) WorkflowsCreate(_a0 []*model.Workflow) error {
|
|||
return r0
|
||||
}
|
||||
|
||||
// WorkflowsReplace provides a mock function with given fields: _a0, _a1
|
||||
func (_m *Store) WorkflowsReplace(_a0 *model.Pipeline, _a1 []*model.Workflow) error {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for WorkflowsReplace")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.Pipeline, []*model.Workflow) error); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// NewStore creates a new instance of Store. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewStore(t interface {
|
||||
|
|
|
@ -171,6 +171,7 @@ type Store interface {
|
|||
// Workflow
|
||||
WorkflowGetTree(*model.Pipeline) ([]*model.Workflow, error)
|
||||
WorkflowsCreate([]*model.Workflow) error
|
||||
WorkflowsReplace(*model.Pipeline, []*model.Workflow) error
|
||||
WorkflowLoad(int64) (*model.Workflow, error)
|
||||
WorkflowUpdate(*model.Workflow) error
|
||||
|
||||
|
|
1
web/components.d.ts
vendored
1
web/components.d.ts
vendored
|
@ -109,7 +109,6 @@ declare module 'vue' {
|
|||
Tab: typeof import('./src/components/layout/scaffold/Tab.vue')['default']
|
||||
Tabs: typeof import('./src/components/layout/scaffold/Tabs.vue')['default']
|
||||
TextField: typeof import('./src/components/form/TextField.vue')['default']
|
||||
UserAPITab: typeof import('./src/components/user/UserAPITab.vue')['default']
|
||||
UserCLIAndAPITab: typeof import('./src/components/user/UserCLIAndAPITab.vue')['default']
|
||||
UserGeneralTab: typeof import('./src/components/user/UserGeneralTab.vue')['default']
|
||||
UserSecretsTab: typeof import('./src/components/user/UserSecretsTab.vue')['default']
|
||||
|
|
Loading…
Reference in a new issue