diff --git a/backend/app/service/app_utils.go b/backend/app/service/app_utils.go index b86175834..b3bbb4224 100644 --- a/backend/app/service/app_utils.go +++ b/backend/app/service/app_utils.go @@ -341,6 +341,24 @@ func upgradeInstall(installId uint, detailId uint, backup bool) error { install.Version = detail.Version install.AppDetailId = detailId + images, err := getImages(install) + if err != nil { + upErr = err + return + } + dockerCli, err := composeV2.NewClient() + if err != nil { + upErr = err + return + } + + for _, image := range images { + if err = dockerCli.PullImage(image, true); err != nil { + upErr = buserr.WithNameAndErr("ErrDockerPullImage", "", err) + return + } + } + if out, err := compose.Down(install.GetComposePath()); err != nil { if out != "" { upErr = errors.New(out) @@ -398,6 +416,26 @@ func getContainerNames(install model.AppInstall) ([]string, error) { 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 + } + imagesMap := make(map[string]struct{}) + for _, service := range project.AllServices() { + imagesMap[service.Image] = struct{}{} + } + var images []string + for k := range imagesMap { + images = append(images, k) + } + return images, nil +} + func coverEnvJsonToStr(envJson string) (string, error) { envMap := make(map[string]interface{}) _ = json.Unmarshal([]byte(envJson), &envMap) diff --git a/backend/buserr/errors.go b/backend/buserr/errors.go index 67a57dc1b..15fed1a8e 100644 --- a/backend/buserr/errors.go +++ b/backend/buserr/errors.go @@ -61,3 +61,18 @@ func WithMap(Key string, maps map[string]interface{}, err error) BusinessError { Err: err, } } + +func WithNameAndErr(Key string, name string, err error) BusinessError { + paramMap := map[string]interface{}{} + if name != "" { + paramMap["name"] = name + } + if err != nil { + paramMap["err"] = err.Error() + } + return BusinessError{ + Msg: Key, + Map: paramMap, + Err: err, + } +} diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index 66ccb7139..9f7734c64 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -45,6 +45,7 @@ ErrImagePullTimeOut: 'Image pull timeout' ErrContainerNotFound: '{{ .name }} container does not exist' ErrContainerMsg: '{{ .name }} container is abnormal, please check the log on the container page for details' ErrAppBackup: '{{ .name }} application backup failed err {{.err}}' +ErrImagePull: '{{ .name }} image pull failed err {{.err}}' #file ErrFileCanNotRead: "File can not read" diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml index 1d9e4312d..76741de93 100644 --- a/backend/i18n/lang/zh-Hant.yaml +++ b/backend/i18n/lang/zh-Hant.yaml @@ -45,6 +45,7 @@ ErrImagePullTimeOut: "鏡像拉取超時" ErrContainerNotFound: '{{ .name }} 容器不存在' ErrContainerMsg: '{{ .name }} 容器異常,具體請在容器頁面查看日誌' ErrAppBackup: '{{ .name }} 應用備份失敗 err {{.err}}' +ErrImagePull: '{{ .name }} 鏡像拉取失敗 err {{.err}}' #file ErrFileCanNotRead: "此文件不支持預覽" diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index 5437b6f1c..81b43f24a 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -45,6 +45,7 @@ ErrImagePullTimeOut: '镜像拉取超时' ErrContainerNotFound: '{{ .name }} 容器不存在' ErrContainerMsg: '{{ .name }} 容器异常,具体请在容器页面查看日志' ErrAppBackup: '{{ .name }} 应用备份失败 err {{.err}}' +ErrImagePull: '镜像拉取失败 {{.err}}' #file ErrFileCanNotRead: "此文件不支持预览" diff --git a/backend/utils/docker/compose.go b/backend/utils/docker/compose.go index 730df21c1..c7a1c5a85 100644 --- a/backend/utils/docker/compose.go +++ b/backend/utils/docker/compose.go @@ -4,11 +4,7 @@ import ( "context" "github.com/compose-spec/compose-go/loader" "github.com/compose-spec/compose-go/types" - "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/flags" "github.com/docker/compose/v2/pkg/api" - "github.com/docker/compose/v2/pkg/compose" - "github.com/docker/docker/client" "github.com/joho/godotenv" "path" "regexp" @@ -21,24 +17,6 @@ type ComposeService struct { project *types.Project } -func NewComposeService(ops ...command.DockerCliOption) (*ComposeService, error) { - apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - return nil, err - } - ops = append(ops, command.WithAPIClient(apiClient), command.WithDefaultContextStoreConfig()) - cli, err := command.NewDockerCli(ops...) - if err != nil { - return nil, err - } - cliOp := flags.NewClientOptions() - if err := cli.Initialize(cliOp); err != nil { - return nil, err - } - service := compose.NewComposeService(cli) - return &ComposeService{service, nil}, nil -} - func (s *ComposeService) SetProject(project *types.Project) { s.project = project for i, s := range project.Services { diff --git a/backend/utils/docker/docker.go b/backend/utils/docker/docker.go index 1c436c420..1d881a3da 100644 --- a/backend/utils/docker/docker.go +++ b/backend/utils/docker/docker.go @@ -72,6 +72,22 @@ func (c Client) DeleteImage(imageID string) error { return nil } +func (c Client) PullImage(imageName string, force bool) error { + if !force { + exist, err := c.CheckImageExist(imageName) + if err != nil { + return err + } + if exist { + return nil + } + } + if _, err := c.cli.ImagePull(context.Background(), imageName, types.ImagePullOptions{}); err != nil { + return err + } + return nil +} + func (c Client) GetImageIDByName(imageName string) (string, error) { filter := filters.NewArgs() filter.Add("reference", imageName) @@ -87,6 +103,18 @@ func (c Client) GetImageIDByName(imageName string) (string, error) { return "", nil } +func (c Client) CheckImageExist(imageName string) (bool, error) { + filter := filters.NewArgs() + filter.Add("reference", imageName) + list, err := c.cli.ImageList(context.Background(), types.ImageListOptions{ + Filters: filter, + }) + if err != nil { + return false, err + } + return len(list) > 0, nil +} + func (c Client) NetworkExist(name string) bool { var options types.NetworkListOptions options.Filters = filters.NewArgs(filters.Arg("name", name))