feat: 创建容器时,端口支持多种形式 (#790)

This commit is contained in:
ssongliu 2023-04-26 15:18:13 +08:00 committed by GitHub
parent 14cc97eb44
commit dd0ca4bcaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 108 additions and 30 deletions

View File

@ -63,8 +63,10 @@ type VolumeHelper struct {
Mode string `json:"mode"` Mode string `json:"mode"`
} }
type PortHelper struct { type PortHelper struct {
ContainerPort int `json:"containerPort"` HostIP string `json:"hostIP"`
HostPort int `json:"hostPort"` HostPort string `json:"hostPort"`
ContainerPort string `json:"containerPort"`
Protocol string `json:"protocol"`
} }
type ContainerLog struct { type ContainerLog struct {

View File

@ -168,8 +168,19 @@ func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error { func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
if len(req.ExposedPorts) != 0 { if len(req.ExposedPorts) != 0 {
for _, port := range req.ExposedPorts { for _, port := range req.ExposedPorts {
if common.ScanPort(port.HostPort) { if strings.Contains(port.HostPort, "-") {
return buserr.WithDetail(constant.ErrPortInUsed, port.HostPort, nil) portStart, _ := strconv.Atoi(strings.Split(port.HostPort, "-")[0])
portEnd, _ := strconv.Atoi(strings.Split(port.HostPort, "-")[1])
for i := portStart; i <= portEnd; i++ {
if common.ScanPort(i) {
return buserr.WithDetail(constant.ErrPortInUsed, i, nil)
}
}
} else {
portItem, _ := strconv.Atoi(port.HostPort)
if common.ScanPort(portItem) {
return buserr.WithDetail(constant.ErrPortInUsed, portItem, nil)
}
} }
} }
} }
@ -202,8 +213,8 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerCreate) error {
if len(req.ExposedPorts) != 0 { if len(req.ExposedPorts) != 0 {
hostConf.PortBindings = make(nat.PortMap) hostConf.PortBindings = make(nat.PortMap)
for _, port := range req.ExposedPorts { for _, port := range req.ExposedPorts {
bindItem := nat.PortBinding{HostPort: strconv.Itoa(port.HostPort)} bindItem := nat.PortBinding{HostPort: port.HostPort, HostIP: port.HostIP}
hostConf.PortBindings[nat.Port(fmt.Sprintf("%d/tcp", port.ContainerPort))] = []nat.PortBinding{bindItem} hostConf.PortBindings[nat.Port(fmt.Sprintf("%s/%s", port.ContainerPort, port.Protocol))] = []nat.PortBinding{bindItem}
} }
} }
if len(req.Volumes) != 0 { if len(req.Volumes) != 0 {

View File

@ -27,8 +27,11 @@ export namespace Container {
restartPolicy: string; restartPolicy: string;
} }
export interface Port { export interface Port {
containerPort: number; host: string;
hostPort: number; hostIP: string;
containerPort: string;
hostPort: string;
protocol: string;
} }
export interface Volume { export interface Volume {
sourceDir: string; sourceDir: string;

View File

@ -455,10 +455,11 @@ const message = {
containerTerminal: 'Terminal', containerTerminal: 'Terminal',
port: 'Port', port: 'Port',
server: 'Host',
serverExample: 'e.g. 80, 80-88, ip:80 or ip:80-88',
contianerExample: 'e.g. 80 or 80-88',
exposePort: 'Expose port', exposePort: 'Expose port',
exposeAll: 'Expose all', exposeAll: 'Expose all',
containerPort: 'Container port',
serverPort: 'Host port',
cmd: 'Command', cmd: 'Command',
cmdHelper: 'Example: echo "hello"', cmdHelper: 'Example: echo "hello"',
autoRemove: 'Auto remove', autoRemove: 'Auto remove',

View File

@ -472,10 +472,11 @@ const message = {
emptyUser: '为空时将使用容器默认的用户登录', emptyUser: '为空时将使用容器默认的用户登录',
port: '端口', port: '端口',
server: '服务器',
serverExample: '例如 80, 80-88, ip:80 或者 ip:80-88',
contianerExample: '例如 80 或者 80-88',
exposePort: '暴露端口', exposePort: '暴露端口',
exposeAll: '暴露所有', exposeAll: '暴露所有',
containerPort: '容器端口',
serverPort: '服务器端口',
cmd: '启动命令', cmd: '启动命令',
cmdHelper: 'echo "hello"', cmdHelper: 'echo "hello"',
autoRemove: '容器退出后自动删除容器', autoRemove: '容器退出后自动删除容器',

View File

@ -29,33 +29,38 @@
<el-card style="width: 100%"> <el-card style="width: 100%">
<table style="width: 100%" class="tab-table"> <table style="width: 100%" class="tab-table">
<tr v-if="form.exposedPorts.length !== 0"> <tr v-if="form.exposedPorts.length !== 0">
<th scope="col" width="48%" align="left"> <th scope="col" width="45%" align="left">
<label>{{ $t('container.serverPort') }}</label> <label>{{ $t('container.server') }}</label>
</th> </th>
<th scope="col" width="48%" align="left"> <th scope="col" width="35%" align="left">
<label>{{ $t('container.containerPort') }}</label> <label>{{ $t('container.container') }}</label>
</th>
<th scope="col" width="20%" align="left">
<label>{{ $t('container.protocol') }}</label>
</th> </th>
<th align="left"></th> <th align="left"></th>
</tr> </tr>
<tr v-for="(row, index) in form.exposedPorts" :key="index"> <tr v-for="(row, index) in form.exposedPorts" :key="index">
<td width="48%"> <td width="45%">
<el-input-number <el-input
:min="0" :placeholder="$t('container.serverExample')"
:max="65535"
style="width: 100%" style="width: 100%"
controls-position="right" v-model="row.host"
v-model.number="row.hostPort"
/> />
</td> </td>
<td width="48%"> <td width="35%">
<el-input-number <el-input
:min="0" :placeholder="$t('container.contianerExample')"
:max="65535"
style="width: 100%" style="width: 100%"
controls-position="right" v-model="row.containerPort"
v-model.number="row.containerPort"
/> />
</td> </td>
<td width="20%">
<el-select v-model="row.protocol" style="width: 100%">
<el-option label="tcp" value="tcp" />
<el-option label="upd" value="upd" />
</el-select>
</td>
<td> <td>
<el-button link style="font-size: 10px" @click="handlePortsDelete(index)"> <el-button link style="font-size: 10px" @click="handlePortsDelete(index)">
{{ $t('commons.button.delete') }} {{ $t('commons.button.delete') }}
@ -204,6 +209,7 @@ import DrawerHeader from '@/components/drawer-header/index.vue';
import { listImage, listVolume, createContainer } from '@/api/modules/container'; import { listImage, listVolume, createContainer } from '@/api/modules/container';
import { Container } from '@/api/interface/container'; import { Container } from '@/api/interface/container';
import { MsgError, MsgSuccess } from '@/utils/message'; import { MsgError, MsgSuccess } from '@/utils/message';
import { checkIp, checkPort } from '@/utils/util';
const loading = ref(false); const loading = ref(false);
@ -277,8 +283,11 @@ const formRef = ref<FormInstance>();
const handlePortsAdd = () => { const handlePortsAdd = () => {
let item = { let item = {
containerPort: null, host: '',
hostPort: null, hostIP: '',
containerPort: '',
hostPort: '',
protocol: 'tcp',
}; };
form.exposedPorts.push(item); form.exposedPorts.push(item);
}; };
@ -327,6 +336,9 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
if (form.cmdStr.length !== 0) { if (form.cmdStr.length !== 0) {
form.cmd = form.cmdStr.split(' '); form.cmd = form.cmdStr.split(' ');
} }
if (!checkPortValid()) {
return;
}
switch (form.memoryUnit) { switch (form.memoryUnit) {
case 'KB': case 'KB':
form.memory = form.memoryItem * 1024; form.memory = form.memoryItem * 1024;
@ -352,6 +364,54 @@ const onSubmit = async (formEl: FormInstance | undefined) => {
}); });
}; };
const checkPortValid = async () => {
if (form.exposedPorts.length === 0) {
return true;
}
for (const port of form.exposedPorts) {
if (port.host.indexOf(':') !== -1) {
port.hostIP = port.host.split(':')[0];
if (checkIp(port.hostIP)) {
MsgError(i18n.global.t('firewall.addressFormatError'));
return false;
}
port.hostPort = port.host.split(':')[1];
} else {
port.hostPort = port.host;
}
if (port.hostPort.indexOf('-') !== -1) {
if (checkPort(port.hostPort.split('-')[0])) {
MsgError(i18n.global.t('firewall.portFormatError'));
return false;
}
if (checkPort(port.hostPort.split('-')[1])) {
MsgError(i18n.global.t('firewall.portFormatError'));
return false;
}
} else {
if (checkPort(port.hostPort)) {
MsgError(i18n.global.t('firewall.portFormatError'));
return false;
}
}
if (port.containerPort.indexOf('-') !== -1) {
if (checkPort(port.containerPort.split('-')[0])) {
MsgError(i18n.global.t('firewall.portFormatError'));
return false;
}
if (checkPort(port.containerPort.split('-')[1])) {
MsgError(i18n.global.t('firewall.portFormatError'));
return false;
}
} else {
if (checkPort(port.containerPort)) {
MsgError(i18n.global.t('firewall.portFormatError'));
return false;
}
}
}
return true;
};
defineExpose({ defineExpose({
acceptParams, acceptParams,
}); });