feat: 卸载应用支持删除镜像 (#6040)

This commit is contained in:
zhengkunwang 2024-08-06 14:41:40 +08:00 committed by GitHub
parent 370463366b
commit e59072e5a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 81 additions and 7 deletions

1
.gitignore vendored
View file

@ -49,6 +49,7 @@ core/xpack
core/router/entry_xpack.go core/router/entry_xpack.go
core/server/init_xpack.go core/server/init_xpack.go
core/utils/xpack/xpack_xpack.go core/utils/xpack/xpack_xpack.go
xpack
.history/ .history/
dist/ dist/

View file

@ -2,6 +2,7 @@ package request
import ( import (
"github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto"
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/constant"
) )
@ -73,6 +74,7 @@ type AppInstalledOperate struct {
PullImage bool `json:"pullImage"` PullImage bool `json:"pullImage"`
DockerCompose string `json:"dockerCompose"` DockerCompose string `json:"dockerCompose"`
TaskID string `json:"taskID"` TaskID string `json:"taskID"`
DeleteImage bool `json:"deleteImage"`
} }
type AppInstallUpgrade struct { type AppInstallUpgrade struct {
@ -84,6 +86,14 @@ type AppInstallUpgrade struct {
TaskID string `json:"taskID"` TaskID string `json:"taskID"`
} }
type AppInstallDelete struct {
Install model.AppInstall
DeleteBackup bool `json:"deleteBackup"`
ForceDelete bool `json:"forceDelete"`
DeleteDB bool `json:"deleteDB"`
DeleteImage bool `json:"deleteImage"`
}
type AppInstalledUpdate struct { type AppInstalledUpdate struct {
InstallId uint `json:"installId" validate:"required"` InstallId uint `json:"installId" validate:"required"`
Params map[string]interface{} `json:"params" validate:"required"` Params map[string]interface{} `json:"params" validate:"required"`

View file

@ -259,7 +259,14 @@ func (a *AppInstallService) Operate(req request.AppInstalledOperate) error {
} }
return syncAppInstallStatus(&install, false) return syncAppInstallStatus(&install, false)
case constant.Delete: case constant.Delete:
if err := deleteAppInstall(install, req.DeleteBackup, req.ForceDelete, req.DeleteDB); err != nil && !req.ForceDelete { deleteReq := request.AppInstallDelete{
Install: install,
DeleteBackup: req.DeleteBackup,
ForceDelete: req.ForceDelete,
DeleteDB: req.DeleteDB,
DeleteImage: req.DeleteImage,
}
if err = deleteAppInstall(deleteReq); err != nil && !req.ForceDelete {
return err return err
} }
return nil return nil

View file

@ -314,13 +314,14 @@ func createLink(ctx context.Context, installTask *task.Task, app model.App, appI
return nil return nil
} }
func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete bool, deleteDB bool) error { func deleteAppInstall(deleteReq request.AppInstallDelete) error {
op := files.NewFileOp() op := files.NewFileOp()
install := deleteReq.Install
appDir := install.GetPath() appDir := install.GetPath()
dir, _ := os.Stat(appDir) dir, _ := os.Stat(appDir)
if dir != nil { if dir != nil {
out, err := compose.Down(install.GetComposePath()) out, err := compose.Down(install.GetComposePath())
if err != nil && !forceDelete { if err != nil && !deleteReq.ForceDelete {
return handleErr(install, err, out) return handleErr(install, err, out)
} }
//TODO use task //TODO use task
@ -328,13 +329,29 @@ func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete b
_, _ = compose.Up(install.GetComposePath()) _, _ = compose.Up(install.GetComposePath())
return err return err
} }
if deleteReq.DeleteImage {
images, _ := getImages(install)
client, err := docker.NewClient()
if err != nil {
return err
}
defer client.Close()
for _, image := range images {
imageID, err := client.GetImageIDByName(image)
if err == nil {
if err = client.DeleteImage(imageID); err != nil {
global.LOG.Errorf("delete image %s error %s", image, err.Error())
}
}
}
}
} }
tx, ctx := helper.GetTxAndContext() tx, ctx := helper.GetTxAndContext()
defer tx.Rollback() defer tx.Rollback()
if err := appInstallRepo.Delete(ctx, install); err != nil { if err := appInstallRepo.Delete(ctx, install); err != nil {
return err return err
} }
if err := deleteLink(ctx, &install, deleteDB, forceDelete, deleteBackup); err != nil && !forceDelete { if err := deleteLink(ctx, &install, deleteReq.DeleteDB, deleteReq.ForceDelete, deleteReq.DeleteBackup); err != nil && !deleteReq.ForceDelete {
return err return err
} }
@ -374,7 +391,7 @@ func deleteAppInstall(install model.AppInstall, deleteBackup bool, forceDelete b
if _, err := os.Stat(uploadDir); err == nil { if _, err := os.Stat(uploadDir); err == nil {
_ = os.RemoveAll(uploadDir) _ = os.RemoveAll(uploadDir)
} }
if deleteBackup { if deleteReq.DeleteBackup {
localDir, _ := loadLocalDir() localDir, _ := loadLocalDir()
backupDir := path.Join(localDir, fmt.Sprintf("app/%s/%s", install.App.Key, install.Name)) backupDir := path.Join(localDir, fmt.Sprintf("app/%s/%s", install.App.Key, install.Name))
if _, err := os.Stat(backupDir); err == nil { if _, err := os.Stat(backupDir); err == nil {
@ -691,6 +708,22 @@ func getContainerNames(install model.AppInstall) ([]string, error) {
return containerNames, nil return containerNames, nil
} }
func getImages(install model.AppInstall) ([]string, error) {
envStr, err := coverEnvJsonToStr(install.Env)
if err != nil {
return nil, err
}
project, err := composeV2.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), []byte(envStr), true)
if err != nil {
return nil, err
}
var images []string
for _, service := range project.AllServices() {
images = append(images, service.Image)
}
return images, nil
}
func coverEnvJsonToStr(envJson string) (string, error) { func coverEnvJsonToStr(envJson string) (string, error) {
envMap := make(map[string]interface{}) envMap := make(map[string]interface{})
_ = json.Unmarshal([]byte(envJson), &envMap) _ = json.Unmarshal([]byte(envJson), &envMap)

View file

@ -479,7 +479,13 @@ func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error {
if checkIsLinkApp(website) && req.DeleteApp { if checkIsLinkApp(website) && req.DeleteApp {
appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) appInstall, _ := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
if appInstall.ID > 0 { if appInstall.ID > 0 {
if err = deleteAppInstall(appInstall, true, req.ForceDelete, true); err != nil && !req.ForceDelete { deleteReq := request.AppInstallDelete{
Install: appInstall,
ForceDelete: req.ForceDelete,
DeleteBackup: true,
DeleteDB: true,
}
if err = deleteAppInstall(deleteReq); err != nil && !req.ForceDelete {
return err return err
} }
} }

View file

@ -188,6 +188,7 @@ export namespace App {
detailId?: number; detailId?: number;
forceDelete?: boolean; forceDelete?: boolean;
deleteBackup?: boolean; deleteBackup?: boolean;
deleteImage?: boolean;
} }
export interface AppInstalledSearch extends ReqPage { export interface AppInstalledSearch extends ReqPage {

View file

@ -1856,6 +1856,10 @@ const message = {
useCustom: 'Customize docker-compose.yml', useCustom: 'Customize docker-compose.yml',
useCustomHelper: useCustomHelper:
'Using a custom docker-compose.yml file may cause the application upgrade to fail. If it is not necessary, do not check it', 'Using a custom docker-compose.yml file may cause the application upgrade to fail. If it is not necessary, do not check it',
diffHelper:
'The left side is the old version, the right side is the new version. After editing, click to save the custom version',
deleteImage: 'Delete Image',
deleteImageHelper: 'Delete the image related to the application. The task will not stop if deletion fails',
}, },
website: { website: {
website: 'Website', website: 'Website',

View file

@ -1723,7 +1723,9 @@ const message = {
useDefault: '使用預設版本', useDefault: '使用預設版本',
useCustom: '自訂 docker-compose.yml', useCustom: '自訂 docker-compose.yml',
useCustomHelper: '使用自訂 docker-compose.yml 文件可能會導致應用程式升級失敗如無必要請勿勾選', useCustomHelper: '使用自訂 docker-compose.yml 文件可能會導致應用程式升級失敗如無必要請勿勾選',
diffHelper: '左側為舊版本右側為新版編輯之後點選使用自訂版本儲存', diffHelper: '左側為舊版本右側為新版編輯之後點擊使用自訂版本保存',
deleteImage: '刪除鏡像',
deleteImageHelper: '刪除應用相關鏡像刪除失敗任務不會終止',
}, },
website: { website: {
website: '網站', website: '網站',

View file

@ -1725,6 +1725,8 @@ const message = {
useCustom: '自定义 docker-compose.yml', useCustom: '自定义 docker-compose.yml',
useCustomHelper: '使用自定义 docker-compose.yml 文件可能会导致应用升级失败如无必要请勿勾选', useCustomHelper: '使用自定义 docker-compose.yml 文件可能会导致应用升级失败如无必要请勿勾选',
diffHelper: '左侧为旧版本右侧为新版编辑之后点击使用自定义版本保存', diffHelper: '左侧为旧版本右侧为新版编辑之后点击使用自定义版本保存',
deleteImage: '删除镜像',
deleteImageHelper: '删除应用相关镜像删除失败任务不会终止',
}, },
website: { website: {
website: '网站', website: '网站',

View file

@ -19,6 +19,12 @@
{{ $t('app.deleteBackupHelper') }} {{ $t('app.deleteBackupHelper') }}
</span> </span>
</el-form-item> </el-form-item>
<el-form-item>
<el-checkbox v-model="deleteReq.deleteImage" :label="$t('app.deleteImage')" />
<span class="input-help">
{{ $t('app.deleteImageHelper') }}
</span>
</el-form-item>
<el-form-item v-if="appType === 'website'"> <el-form-item v-if="appType === 'website'">
<el-checkbox v-model="deleteReq.deleteDB" :label="$t('app.deleteDB')" /> <el-checkbox v-model="deleteReq.deleteDB" :label="$t('app.deleteDB')" />
<span class="input-help"> <span class="input-help">
@ -57,6 +63,7 @@ let deleteReq = ref({
deleteBackup: false, deleteBackup: false,
forceDelete: false, forceDelete: false,
deleteDB: true, deleteDB: true,
deleteImage: true,
}); });
let open = ref(false); let open = ref(false);
let loading = ref(false); let loading = ref(false);
@ -80,6 +87,7 @@ const acceptParams = async (app: App.AppInstallDto) => {
deleteBackup: false, deleteBackup: false,
forceDelete: false, forceDelete: false,
deleteDB: true, deleteDB: true,
deleteImage: true,
}; };
deleteInfo.value = ''; deleteInfo.value = '';
deleteReq.value.installId = app.id; deleteReq.value.installId = app.id;