mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-29 20:00:30 +00:00
Let remove be a remove (#593)
* UI: let remove be a remove * UI: add deactivate repo btn * Store: DeleteRepo also delete related * Store: more test coverage Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
parent
4cbdacb21c
commit
fe6c999160
9 changed files with 163 additions and 23 deletions
|
@ -199,7 +199,6 @@ func GetRepoBranches(c *gin.Context) {
|
|||
|
||||
func DeleteRepo(c *gin.Context) {
|
||||
remove, _ := strconv.ParseBool(c.Query("remove"))
|
||||
remote := server.Config.Services.Remote
|
||||
_store := store.FromContext(c)
|
||||
|
||||
repo := session.Repo(c)
|
||||
|
@ -208,21 +207,19 @@ func DeleteRepo(c *gin.Context) {
|
|||
repo.IsActive = false
|
||||
repo.UserID = 0
|
||||
|
||||
err := _store.UpdateRepo(repo)
|
||||
if err != nil {
|
||||
if err := _store.UpdateRepo(repo); err != nil {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
if remove {
|
||||
err := _store.DeleteRepo(repo)
|
||||
if err != nil {
|
||||
if err := _store.DeleteRepo(repo); err != nil {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := remote.Deactivate(c, user, repo, server.Config.Server.Host); err != nil {
|
||||
if err := server.Config.Services.Remote.Deactivate(c, user, repo, server.Config.Server.Host); err != nil {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -18,4 +18,5 @@ type Logs struct {
|
|||
ID int64 `xorm:"pk autoincr 'log_id'"`
|
||||
ProcID int64 `xorm:"UNIQUE 'log_job_id'"`
|
||||
Data []byte `xorm:"log_data"`
|
||||
// TODO: add create timestamp
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ package datastore
|
|||
import (
|
||||
"time"
|
||||
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/woodpecker-ci/woodpecker/server/model"
|
||||
)
|
||||
|
||||
|
@ -108,6 +110,7 @@ func (s storage) CreateBuild(build *model.Build, procList ...*model.Proc) error
|
|||
}
|
||||
|
||||
for i := range procList {
|
||||
procList[i].BuildID = build.ID
|
||||
// only Insert set auto created ID back to object
|
||||
if _, err := sess.Insert(procList[i]); err != nil {
|
||||
return err
|
||||
|
@ -121,3 +124,27 @@ func (s storage) UpdateBuild(build *model.Build) error {
|
|||
_, err := s.engine.ID(build.ID).AllCols().Update(build)
|
||||
return err
|
||||
}
|
||||
|
||||
func deleteBuild(sess *xorm.Session, buildID int64) error {
|
||||
// delete related procs
|
||||
for startProcs := 0; ; startProcs += perPage {
|
||||
procIDs := make([]int64, 0, perPage)
|
||||
if err := sess.Limit(perPage, startProcs).Table("procs").Cols("proc_id").Where("proc_build_id = ?", buildID).Find(&procIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(procIDs) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for i := range procIDs {
|
||||
if err := deleteProc(sess, procIDs[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, err := sess.Where("build_id = ?", buildID).Delete(new(model.BuildConfig)); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := sess.ID(buildID).Delete(new(model.Build))
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
package datastore
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/woodpecker-ci/woodpecker/server/model"
|
||||
)
|
||||
|
||||
|
@ -84,3 +86,14 @@ func (s storage) ProcClear(build *model.Build) error {
|
|||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func deleteProc(sess *xorm.Session, procID int64) error {
|
||||
if _, err := sess.Where("log_job_id = ?", procID).Delete(new(model.Logs)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := sess.Where("file_proc_id = ?", procID).Delete(new(model.File)); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := sess.ID(procID).Delete(new(model.Proc))
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -54,9 +54,51 @@ func (s storage) UpdateRepo(repo *model.Repo) error {
|
|||
}
|
||||
|
||||
func (s storage) DeleteRepo(repo *model.Repo) error {
|
||||
_, err := s.engine.ID(repo.ID).Delete(new(model.Repo))
|
||||
// TODO: delete related within a session
|
||||
return err
|
||||
const batchSize = perPage
|
||||
sess := s.engine.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sess.Where("sender_repo_id = ?", repo.ID).Delete(new(model.Sender)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := sess.Where("config_repo_id = ?", repo.ID).Delete(new(model.Config)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := sess.Where("perm_repo_id = ?", repo.ID).Delete(new(model.Perm)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := sess.Where("registry_repo_id = ?", repo.ID).Delete(new(model.Registry)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := sess.Where("secret_repo_id = ?", repo.ID).Delete(new(model.Secret)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete related builds
|
||||
for startBuilds := 0; ; startBuilds += batchSize {
|
||||
buildIDs := make([]int64, 0, batchSize)
|
||||
if err := sess.Limit(batchSize, startBuilds).Table("builds").Cols("build_id").Where("build_repo_id = ?", repo.ID).Find(&buildIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(buildIDs) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for i := range buildIDs {
|
||||
if err := deleteBuild(sess, buildIDs[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := sess.ID(repo.ID).Delete(new(model.Repo)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// RepoList list all repos where permissions fo specific user are stored
|
||||
|
|
|
@ -362,7 +362,19 @@ func TestRepoBatch(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRepoCrud(t *testing.T) {
|
||||
store, closer := newTestStore(t, new(model.Repo), new(model.User), new(model.Perm))
|
||||
store, closer := newTestStore(t,
|
||||
new(model.Repo),
|
||||
new(model.User),
|
||||
new(model.Perm),
|
||||
new(model.Build),
|
||||
new(model.BuildConfig),
|
||||
new(model.Logs),
|
||||
new(model.Proc),
|
||||
new(model.File),
|
||||
new(model.Secret),
|
||||
new(model.Sender),
|
||||
new(model.Registry),
|
||||
new(model.Config))
|
||||
defer closer()
|
||||
|
||||
repo := model.Repo{
|
||||
|
@ -372,16 +384,40 @@ func TestRepoCrud(t *testing.T) {
|
|||
Name: "test",
|
||||
}
|
||||
assert.NoError(t, store.CreateRepo(&repo))
|
||||
_, err1 := store.GetRepo(repo.ID)
|
||||
err2 := store.DeleteRepo(&repo)
|
||||
_, err3 := store.GetRepo(repo.ID)
|
||||
if err1 != nil {
|
||||
t.Errorf("Unexpected error: select repository: %s", err1)
|
||||
build := model.Build{
|
||||
RepoID: repo.ID,
|
||||
}
|
||||
if err2 != nil {
|
||||
t.Errorf("Unexpected error: delete repository: %s", err2)
|
||||
proc := model.Proc{
|
||||
Name: "a proc",
|
||||
}
|
||||
if err3 == nil {
|
||||
t.Errorf("Expected error: sql.ErrNoRows")
|
||||
assert.NoError(t, store.CreateBuild(&build, &proc))
|
||||
|
||||
// create unrelated
|
||||
repoUnrelated := model.Repo{
|
||||
UserID: 2,
|
||||
FullName: "x/x",
|
||||
Owner: "x",
|
||||
Name: "x",
|
||||
}
|
||||
assert.NoError(t, store.CreateRepo(&repoUnrelated))
|
||||
buildUnrelated := model.Build{
|
||||
RepoID: repoUnrelated.ID,
|
||||
}
|
||||
procUnrelated := model.Proc{
|
||||
Name: "a unrelated proc",
|
||||
}
|
||||
assert.NoError(t, store.CreateBuild(&buildUnrelated, &procUnrelated))
|
||||
|
||||
_, err := store.GetRepo(repo.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, store.DeleteRepo(&repo))
|
||||
_, err = store.GetRepo(repo.ID)
|
||||
assert.Error(t, err)
|
||||
|
||||
procCount, err := store.engine.Count(new(model.Proc))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, procCount)
|
||||
buildCount, err := store.engine.Count(new(model.Build))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 1, buildCount)
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
<i-ic-round-light-mode v-else-if="name === 'light'" class="h-6 w-6" />
|
||||
<i-mdi-sync v-else-if="name === 'sync'" class="h-6 w-6" />
|
||||
<i-ic-baseline-healing v-else-if="name === 'heal'" class="h-6 w-6" />
|
||||
<i-bx-bx-power-off v-else-if="name === 'turn-off'" class="h-6 w-6" />
|
||||
<div v-else-if="name === 'blank'" class="h-6 w-6" />
|
||||
</template>
|
||||
|
||||
|
@ -68,7 +69,8 @@ export type IconNames =
|
|||
| 'dark'
|
||||
| 'light'
|
||||
| 'sync'
|
||||
| 'heal';
|
||||
| 'heal'
|
||||
| 'turn-off';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Icon',
|
||||
|
|
|
@ -14,6 +14,15 @@
|
|||
@click="repairRepo"
|
||||
/>
|
||||
|
||||
<Button
|
||||
class="mr-auto mt-4"
|
||||
color="blue"
|
||||
start-icon="turn-off"
|
||||
text="Disable repository"
|
||||
:is-loading="isDeactivatingRepo"
|
||||
@click="deactivateRepo"
|
||||
/>
|
||||
|
||||
<Button
|
||||
class="mr-auto mt-4"
|
||||
color="red"
|
||||
|
@ -65,7 +74,7 @@ export default defineComponent({
|
|||
|
||||
// TODO use proper dialog
|
||||
// eslint-disable-next-line no-alert, no-restricted-globals
|
||||
if (!confirm('All data will be lost after this action!!!\n\nDo you really want to procceed?')) {
|
||||
if (!confirm('All data will be lost after this action!!!\n\nDo you really want to proceed?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -74,11 +83,23 @@ export default defineComponent({
|
|||
await router.replace({ name: 'repos' });
|
||||
});
|
||||
|
||||
const { doSubmit: deactivateRepo, isLoading: isDeactivatingRepo } = useAsyncAction(async () => {
|
||||
if (!repo) {
|
||||
throw new Error('Unexpected: Repo should be set');
|
||||
}
|
||||
|
||||
await apiClient.deleteRepo(repo.value.owner, repo.value.name, false);
|
||||
notifications.notify({ title: 'Repository disabled', type: 'success' });
|
||||
await router.replace({ name: 'repos' });
|
||||
});
|
||||
|
||||
return {
|
||||
isRepairingRepo,
|
||||
isDeletingRepo,
|
||||
isDeactivatingRepo,
|
||||
deleteRepo,
|
||||
repairRepo,
|
||||
deactivateRepo,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -31,8 +31,9 @@ export default class WoodpeckerClient extends ApiClient {
|
|||
return this._patch(`/api/repos/${owner}/${repo}`, repoSettings);
|
||||
}
|
||||
|
||||
deleteRepo(owner: string, repo: string): Promise<unknown> {
|
||||
return this._delete(`/api/repos/${owner}/${repo}`);
|
||||
deleteRepo(owner: string, repo: string, remove = true): Promise<unknown> {
|
||||
const query = encodeQueryString({ remove });
|
||||
return this._delete(`/api/repos/${owner}/${repo}?${query}`);
|
||||
}
|
||||
|
||||
repairRepo(owner: string, repo: string): Promise<unknown> {
|
||||
|
|
Loading…
Reference in a new issue