fix: 解决容器镜像为空导致升级失败的问题 (#3287)

Refs #3278
This commit is contained in:
ssongliu 2023-12-12 15:22:09 +08:00 committed by GitHub
parent 168b6b8667
commit ed6735e610
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 60 additions and 45 deletions

View file

@ -407,6 +407,7 @@ func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.Contai
} }
} }
data.AutoRemove = oldContainer.HostConfig.AutoRemove data.AutoRemove = oldContainer.HostConfig.AutoRemove
data.Privileged = oldContainer.HostConfig.Privileged
data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts
data.RestartPolicy = oldContainer.HostConfig.RestartPolicy.Name data.RestartPolicy = oldContainer.HostConfig.RestartPolicy.Name
if oldContainer.HostConfig.NanoCPUs != 0 { if oldContainer.HostConfig.NanoCPUs != 0 {
@ -440,7 +441,7 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error {
if !req.ForcePull { if !req.ForcePull {
return err return err
} }
global.LOG.Errorf("force pull image %s failed, err: %v", req.Image, err) return fmt.Errorf("pull image %s failed, err: %v", req.Image, err)
} }
} }
@ -483,7 +484,7 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error {
if !req.ForcePull { if !req.ForcePull {
return err return err
} }
global.LOG.Errorf("force pull image %s failed, err: %v", req.Image, err) return fmt.Errorf("pull image %s failed, err: %v", req.Image, err)
} }
} }
config := oldContainer.Config config := oldContainer.Config

View file

@ -485,31 +485,20 @@ func (u *FirewallService) updatePingStatus(enable string) error {
if err != nil { if err != nil {
return err return err
} }
hasV4Line, hasV6Line := false, false
if _, err := os.Stat("/proc/sys/net/ipv6/icmp_echo_ignore_all"); err != nil {
hasV6Line = true
}
files := strings.Split(string(lineBytes), "\n") files := strings.Split(string(lineBytes), "\n")
var newFiles []string var newFiles []string
hasLine := false
for _, line := range files { for _, line := range files {
if strings.HasPrefix(strings.ReplaceAll(line, " ", ""), "net/ipv4/icmp_echo_ignore_all") && !hasV4Line { if strings.Contains(line, "net/ipv4/icmp_echo_ignore_all") || strings.HasPrefix(line, "net/ipv4/icmp_echo_ignore_all") {
newFiles = append(newFiles, "net/ipv4/icmp_echo_ignore_all="+enable) newFiles = append(newFiles, "net/ipv4/icmp_echo_ignore_all="+enable)
hasV4Line = true hasLine = true
continue } else {
newFiles = append(newFiles, line)
} }
if strings.HasPrefix(strings.ReplaceAll(line, " ", ""), "net/ipv6/icmp_echo_ignore_all") && !hasV6Line {
newFiles = append(newFiles, "net/ipv6/icmp_echo_ignore_all="+enable)
hasV6Line = true
continue
}
newFiles = append(newFiles, line)
} }
if !hasV4Line { if !hasLine {
newFiles = append(newFiles, "net/ipv4/icmp_echo_ignore_all="+enable) newFiles = append(newFiles, "net/ipv4/icmp_echo_ignore_all="+enable)
} }
if !hasV6Line {
newFiles = append(newFiles, "net/ipv6/icmp_echo_ignore_all="+enable)
}
file, err := os.OpenFile(confPath, os.O_WRONLY|os.O_TRUNC, 0666) file, err := os.OpenFile(confPath, os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil { if err != nil {
return err return err
@ -526,6 +515,11 @@ func (u *FirewallService) updatePingStatus(enable string) error {
if err != nil { if err != nil {
return fmt.Errorf("update ping status failed, err: %v", stdout) return fmt.Errorf("update ping status failed, err: %v", stdout)
} }
handle := "-A"
if enable == "1" {
handle = "-D"
}
_, _ = cmd.Execf("%s ip6tables %s INPUT -p icmpv6 --icmpv6-type echo-request -j DROP", cmd.SudoHandleCmd(), handle)
return nil return nil
} }

View file

@ -597,7 +597,7 @@ const message = {
'The upgrade operation requires rebuilding the container, and any non-persistent data will be lost. Do you want to continue?', 'The upgrade operation requires rebuilding the container, and any non-persistent data will be lost. Do you want to continue?',
oldImage: 'Current image', oldImage: 'Current image',
targetImage: 'Target image', targetImage: 'Target image',
targetImageHelper: 'Please enter the target image version', imageLoadErr: 'System did not detect the container image name, please manually enter the full image name:tag ',
appHelper: appHelper:
'This container is sourced from the application store. Upgrading it may cause the service to be unavailable.', 'This container is sourced from the application store. Upgrading it may cause the service to be unavailable.',

View file

@ -578,7 +578,7 @@ const message = {
upgradeWarning2: '升級操作需要重建容器任何未持久化的數據將會丟失是否繼續', upgradeWarning2: '升級操作需要重建容器任何未持久化的數據將會丟失是否繼續',
oldImage: '當前鏡像', oldImage: '當前鏡像',
targetImage: '目標鏡像', targetImage: '目標鏡像',
targetImageHelper: '請輸入目標鏡像版本', imageLoadErr: '系統未檢測到容器的鏡像名稱請手動輸入完整的鏡像名稱:標籤',
appHelper: '該容器來源於應用商店升級可能導致該服務不可用', appHelper: '該容器來源於應用商店升級可能導致該服務不可用',
input: '手動輸入', input: '手動輸入',

View file

@ -579,7 +579,7 @@ const message = {
upgradeWarning2: '升级操作需要重建容器任何未持久化的数据将会丢失是否继续', upgradeWarning2: '升级操作需要重建容器任何未持久化的数据将会丢失是否继续',
oldImage: '当前镜像', oldImage: '当前镜像',
targetImage: '目标镜像', targetImage: '目标镜像',
targetImageHelper: '请输入目标镜像版本', imageLoadErr: '系统未检测到容器的镜像名称请手动输入完整的镜像名称:标签',
appHelper: '该容器来源于应用商店升级可能导致该服务不可用', appHelper: '该容器来源于应用商店升级可能导致该服务不可用',
input: '手动输入', input: '手动输入',

View file

@ -162,6 +162,7 @@ const acceptParams = (): void => {
form.path = ''; form.path = '';
form.file = ''; form.file = '';
form.template = null; form.template = null;
onCreating.value = false;
loadTemplates(); loadTemplates();
loadPath(); loadPath();
}; };

View file

@ -15,27 +15,33 @@
/> />
<el-form @submit.prevent ref="formRef" :model="form" label-position="top"> <el-form @submit.prevent ref="formRef" :model="form" label-position="top">
<el-form-item :label="$t('container.oldImage')" prop="oldImage"> <el-form-item :label="$t('container.oldImage')" prop="oldImage">
<el-tooltip placement="top-start" :content="form.imageName" v-if="form.imageName.length > 50"> <el-tooltip
<el-tag>{{ form.imageName.substring(0, 50) }}...:{{ form.oldTag }}</el-tag> placement="top-start"
:content="form.oldImageName"
v-if="form.oldImageName.length > 50"
>
<el-tag>{{ form.oldImageName.substring(0, 50) }}...</el-tag>
</el-tooltip> </el-tooltip>
<el-tag v-else>{{ form.imageName }}:{{ form.oldTag }}</el-tag> <el-tag v-else>{{ form.oldImageName }}</el-tag>
</el-form-item> </el-form-item>
<el-form-item prop="newTag" :rules="Rules.imageName"> <el-form-item prop="newImageName" :rules="Rules.imageName">
<template #label> <template #label>
<el-tooltip <el-tooltip
placement="top-start" placement="top-start"
:content="form.imageName" :content="form.imageHelper"
v-if="form.imageName.length > 40" v-if="form.imageHelper.length > 40"
> >
<span> <span>
{{ $t('container.targetImage') + ' (' + form.imageName.substring(0, 40) + '...)' }} {{
$t('container.targetImage') + ' (' + form.imageHelper.substring(0, 40) + '...)'
}}
</span> </span>
</el-tooltip> </el-tooltip>
<span v-else> <span v-else>
{{ $t('container.targetImage') + ' (' + form.imageName + ')' }} {{ $t('container.targetImage') + ' (' + form.imageHelper + ')' }}
</span> </span>
</template> </template>
<el-input v-model="form.newTag" :placeholder="$t('container.targetImageHelper')" /> <el-input v-model="form.newImageName" :placeholder="$t('container.imageNameHelper')" />
<span class="input-help">{{ $t('container.upgradeHelper') }}</span> <span class="input-help">{{ $t('container.upgradeHelper') }}</span>
</el-form-item> </el-form-item>
<el-form-item prop="ignoreCompare"> <el-form-item prop="ignoreCompare">
@ -79,9 +85,9 @@ const loading = ref(false);
const form = reactive({ const form = reactive({
name: '', name: '',
imageName: '', oldImageName: '',
oldTag: '', newImageName: '',
newTag: '', imageHelper: '',
fromApp: false, fromApp: false,
forcePull: false, forcePull: false,
@ -100,11 +106,16 @@ interface DialogProps {
} }
const acceptParams = (props: DialogProps): void => { const acceptParams = (props: DialogProps): void => {
form.name = props.container; form.name = props.container;
form.imageName = props.image.indexOf(':') !== -1 ? props.image.split(':')[0] : props.image; form.oldImageName = props.image;
form.oldTag = props.image.indexOf(':') !== -1 ? props.image.split(':')[1] : 'latest';
form.newTag = form.oldTag;
form.fromApp = props.fromApp; form.fromApp = props.fromApp;
form.ignoreCompare = false; form.ignoreCompare = false;
if (props.image.indexOf('sha256:') !== -1) {
form.imageHelper = i18n.global.t('container.imageLoadErr');
drawerVisible.value = true;
return;
}
form.imageHelper = props.image.indexOf(':') !== -1 ? props.image.split(':')[0] : props.image;
drawerVisible.value = true; drawerVisible.value = true;
}; };
const emit = defineEmits<{ (e: 'search'): void }>(); const emit = defineEmits<{ (e: 'search'): void }>();
@ -113,7 +124,7 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
formEl.validate(async (valid) => { formEl.validate(async (valid) => {
if (!valid) return; if (!valid) return;
if (!form.ignoreCompare && !compareVersion(form.newTag, form.oldTag)) { if (!form.ignoreCompare && !compareVersion(form.newImageName, form.oldImageName)) {
MsgWarning(i18n.global.t('container.upgradeWarning')); MsgWarning(i18n.global.t('container.upgradeWarning'));
return; return;
} }
@ -122,7 +133,7 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
cancelButtonText: i18n.global.t('commons.button.cancel'), cancelButtonText: i18n.global.t('commons.button.cancel'),
}).then(async () => { }).then(async () => {
loading.value = true; loading.value = true;
await upgradeContainer(form.name, form.imageName + ':' + form.newTag, form.forcePull) await upgradeContainer(form.name, form.newImageName, form.forcePull)
.then(() => { .then(() => {
loading.value = false; loading.value = false;
emit('search'); emit('search');
@ -141,14 +152,22 @@ const handleClose = async () => {
}; };
function compareVersion(vNew, vOld) { function compareVersion(vNew, vOld) {
if (vNew === 'latest') { let newImageName = vNew.indexOf(':') !== -1 ? vNew.split(':')[0] : vNew;
let oldImageName = vOld.indexOf(':') !== -1 ? vOld.split(':')[0] : vOld;
if (newImageName !== oldImageName) {
return true; return true;
} }
let v1 = vNew let newTag = vNew.indexOf(':') !== -1 ? vNew.split(':')[1] : 'latest';
if (newTag === 'latest') {
return true;
}
let oldTag = vOld.indexOf(':') !== -1 ? vOld.split(':')[1] : 'latest';
let v1 = newTag
.replace('-', '.') .replace('-', '.')
.replace(/[^\d.]/g, '') .replace(/[^\d.]/g, '')
.split('.'); .split('.');
let v2 = vOld let v2 = oldTag
.replace('-', '.') .replace('-', '.')
.replace(/[^\d.]/g, '') .replace(/[^\d.]/g, '')
.split('.'); .split('.');

View file

@ -202,10 +202,10 @@ const handleClose = () => {
const rules = reactive({ const rules = reactive({
name: [Rules.requiredInput], name: [Rules.requiredInput],
driver: [Rules.requiredSelect], driver: [Rules.requiredSelect],
subnet: [{ validator: checkCidr, trigger: 'blur' }], subnet: [{ validator: checkCidr, trigger: 'blur' }, Rules.requiredInput],
gateway: [{ validator: checkGateway, trigger: 'blur' }], gateway: [{ validator: checkGateway, trigger: 'blur' }],
scope: [{ validator: checkCidr, trigger: 'blur' }], scope: [{ validator: checkCidr, trigger: 'blur' }],
subnetV6: [{ validator: checkFixedCidrV6, trigger: 'blur' }], subnetV6: [{ validator: checkFixedCidrV6, trigger: 'blur' }, Rules.requiredInput],
gatewayV6: [{ validator: checkGatewayV6, trigger: 'blur' }], gatewayV6: [{ validator: checkGatewayV6, trigger: 'blur' }],
scopeV6: [{ validator: checkFixedCidrV6, trigger: 'blur' }], scopeV6: [{ validator: checkFixedCidrV6, trigger: 'blur' }],
}); });

View file

@ -1,5 +1,5 @@
<template> <template>
<el-drawer :close-on-click-modal="false" v-model="drawerVisible" size="30%"> <el-drawer :close-on-click-modal="false" :destroy-on-close="true" v-model="drawerVisible" size="30%">
<template #header> <template #header>
<Header :header="$t('toolbox.fail2ban.' + form.operate + 'IP')" :back="handleClose"></Header> <Header :header="$t('toolbox.fail2ban.' + form.operate + 'IP')" :back="handleClose"></Header>
</template> </template>