diff --git a/.gitignore b/.gitignore
index a02c16ca82fd1114f73a053dd9ad71ef9b4bf29a..70b979c53c49fdf51e194559a59b73ad8f4d86e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -118,3 +118,5 @@ SimpleDocker
yarn.lock
static/
+
+SimpleDocker.tar.gz
diff --git a/App.go b/App.go
index c7ed4fe81227661f106b0ec523124357b57d515b..757ef488669b8136c33061090fb8f216925edb59 100644
--- a/App.go
+++ b/App.go
@@ -14,7 +14,7 @@ import (
"strconv"
)
-var port = flag.Int("port", 4040, "help message for flagname")
+var port = flag.Int("port", 4050, "help message for flagname")
func stopCommand() {
// 读取PID
diff --git a/SimpleDocker.tar.gz b/SimpleDocker.tar.gz
deleted file mode 100644
index a4c9677b8d732fd0cca0ce12f36689f36a7e04a3..0000000000000000000000000000000000000000
Binary files a/SimpleDocker.tar.gz and /dev/null differ
diff --git a/api/ContainerController.go b/api/ContainerController.go
index fc1ea0377e2dd9a3ba90aa3a9a3ce7496e7f648f..2f173a50b6764fb646296e72e4540efe9a7ed20a 100644
--- a/api/ContainerController.go
+++ b/api/ContainerController.go
@@ -3,6 +3,7 @@ package api
import (
"SimpleDocker/docker"
"SimpleDocker/utils"
+ "bytes"
"fmt"
"github.com/astaxie/beego"
"github.com/docker/docker/api/types"
@@ -175,10 +176,29 @@ func (c *ContainerController) GetContainerAllLog(containerId string) {
}
// 准备下载文件
-
logsByte := []byte(logs)
c.Ctx.Output.Header("Content-Type", "application/force-download")
c.Ctx.Output.Header("Content-Disposition", fmt.Sprintf("attachment;filename=%s-all.log", containerId))
c.Ctx.Output.Header("Content-Transfer-Encoding", "binary")
_, _ = c.Ctx.ResponseWriter.Write(logsByte)
}
+
+// 容器导出
+// @router /api/container/:containerId/export [get]
+func (c *ContainerController) ExportContainer(containerId string) {
+ info, err := docker.ExportContainer(containerId)
+ if err != nil {
+ c.Data["json"] = utils.PackageError(err)
+ c.ServeJSON()
+ return
+ }
+
+ // 转换为文件下载
+ buf := new(bytes.Buffer)
+ _, _ = buf.ReadFrom(info)
+ bytesData := buf.Bytes()
+ c.Ctx.Output.Header("Content-Type", "application/force-download")
+ c.Ctx.Output.Header("Content-Disposition", fmt.Sprintf("attachment;filename=%s.tar.gz", containerId))
+ c.Ctx.Output.Header("Content-Transfer-Encoding", "binary")
+ _, _ = c.Ctx.ResponseWriter.Write(bytesData)
+}
diff --git a/api/ImageController.go b/api/ImageController.go
index 69f8c2bb5d5023f17a2962c673159a9b5248dc2b..d86878d18d1794e6b73a3c6df6d6ca827f19dc04 100644
--- a/api/ImageController.go
+++ b/api/ImageController.go
@@ -9,6 +9,7 @@ import (
"github.com/astaxie/beego/logs"
"strconv"
"strings"
+ "time"
)
type ImageController struct {
@@ -109,8 +110,29 @@ func (c *ImageController) PullImage() {
}
/** 导入Image */
-// @router
+// @router /api/image/import
func (c *ImageController) ImportImage() {
- c.Data["json"] = utils.PackageErrorMsg("暂不支持导入镜像,请期待后续版本")
+ file, _, err := c.GetFile("file")
+ if err != nil {
+ c.Data["json"] = "获取文件失败,请重新上传文件"
+ c.ServeJSON()
+ return
+ }
+ defer file.Close()
+ saveFilePath := "/tmp/" + strconv.FormatInt(time.Now().Unix(), 10)
+ err = c.SaveToFile("file", saveFilePath)
+ if err != nil {
+ c.Data["json"] = "保存文件失败,请检查文件是否存在"
+ c.ServeJSON()
+ return
+ }
+ _, err = docker.ImportImage(saveFilePath)
+ if err != nil {
+ c.Data["json"] = utils.PackageError(err)
+ c.ServeJSON()
+ return
+ }
+
+ c.Data["json"] = utils.Success()
c.ServeJSON()
}
diff --git a/api/LoginController.go b/api/LoginController.go
new file mode 100644
index 0000000000000000000000000000000000000000..d497098a294cfe04a8a1c9f8c03e801aaf493a7f
--- /dev/null
+++ b/api/LoginController.go
@@ -0,0 +1,41 @@
+package api
+
+import (
+ "SimpleDocker/docker"
+ "SimpleDocker/utils"
+ "github.com/astaxie/beego"
+ "strings"
+)
+
+type LoginController struct {
+ beego.Controller
+}
+
+// router /api/docker/login
+func (c *LoginController) Login() {
+
+ name := c.Ctx.Input.Query("username")
+ password := c.Ctx.Input.Query("password")
+
+ if name = strings.Trim(name, " "); name == "" {
+ c.Data["json"] = utils.PackageErrorMsg("登录失败,用户名无效")
+ c.ServeJSON()
+ return
+ }
+
+ if name = strings.Trim(password, " "); password == "" {
+ c.Data["json"] = utils.PackageErrorMsg("登录失败,密码无效")
+ c.ServeJSON()
+ return
+ }
+
+ _, err := docker.Login(name, password)
+ if err != nil {
+ c.Data["json"] = utils.PackageErrorMsg("登录失败,密码无效")
+ c.ServeJSON()
+ return
+ }
+
+ c.Data["json"] = utils.Success()
+ c.ServeJSON()
+}
diff --git a/context/Context.go b/context/Context.go
index 0d07bc6786e25fa631f92094208269e138713ce2..5e84446a41eead97e9041cd91d9e7be12fa12fdd 100644
--- a/context/Context.go
+++ b/context/Context.go
@@ -4,16 +4,26 @@ import (
"context"
"github.com/astaxie/beego/logs"
"github.com/docker/docker/client"
+ "net"
+ "net/http"
)
var Ctx context.Context
var Cli *client.Client
+var _ http.Client
func init() {
var err error
Ctx = context.Background()
Cli, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
+ _ = http.Client{
+ Transport: &http.Transport{
+ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
+ return net.Dial("unix", "/var/run/docker.sock")
+ },
+ },
+ }
if err != nil {
logs.Info("初始化Docker上下文....FAIL!")
return
diff --git a/docker/auth.go b/docker/auth.go
new file mode 100644
index 0000000000000000000000000000000000000000..52fe4d002e560c0435e198e4ab97e3fca31d6cb9
--- /dev/null
+++ b/docker/auth.go
@@ -0,0 +1,23 @@
+package docker
+
+import (
+ "SimpleDocker/context"
+ "github.com/docker/docker/api/types"
+ "github.com/docker/docker/api/types/registry"
+)
+
+type UsernamePassword map[string]string
+
+/** Docker 登录到镜像中心 */
+func Login(username string, password string) (registry.AuthenticateOKBody, error) {
+ authConfig := types.AuthConfig{Username: username, Password: password}
+ return context.Cli.RegistryLogin(context.Ctx, authConfig)
+}
+
+/** 获取登录记录密码 */
+func GetLoginRecord() []UsernamePassword {
+ u := make(map[string]string)
+ u["user"] = "user"
+ u["password"] = "password"
+ return []UsernamePassword{u}
+}
diff --git a/docker/Container.go b/docker/container.go
similarity index 94%
rename from docker/Container.go
rename to docker/container.go
index 518f008d13323f70db3b95ef828872f6b566dfe9..b07245e6e0d6cd695fa7614977c2fd3d58a936e7 100644
--- a/docker/Container.go
+++ b/docker/container.go
@@ -6,6 +6,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat"
+ "io"
"time"
)
@@ -83,6 +84,7 @@ func GetContainerLog(containerId string, tail string) (string, error) {
return buf.String(), nil
}
-func GetContainer() {
-
+/** 导出容器 */
+func ExportContainer(containerId string) (io.ReadCloser, error) {
+ return context.Cli.ContainerExport(context.Ctx, containerId)
}
diff --git a/docker/image.go b/docker/image.go
index 61960b409b661d40757d045397786fcd77ed7563..5813999e7a619cdd4ab1813c96f5935194eefd77 100644
--- a/docker/image.go
+++ b/docker/image.go
@@ -3,7 +3,9 @@ package docker
import (
"SimpleDocker/context"
"github.com/docker/docker/api/types"
+ "github.com/pkg/errors"
"io"
+ "os"
)
func GetImageList() ([]types.ImageSummary, error) {
@@ -44,3 +46,12 @@ func DeleteImage(imageId string, forge bool) error {
func SaveImage(imageId string) (io.ReadCloser, error) {
return context.Cli.ImageSave(context.Ctx, []string{imageId})
}
+
+/** 导入镜像 */
+func ImportImage(filePath string) (types.ImageLoadResponse, error) {
+ open, err := os.OpenFile(filePath, os.O_RDWR, 666)
+ if err != nil {
+ return types.ImageLoadResponse{}, errors.New("读取文件失败")
+ }
+ return context.Cli.ImageLoad(context.Ctx, open, false)
+}
diff --git a/go.mod b/go.mod
index e56145ad6cfd8a81ad2b42dfbc5edf5cef6414a5..844e992f13ab1c1fdab2b037fcf7d5a45d497c03 100644
--- a/go.mod
+++ b/go.mod
@@ -17,6 +17,7 @@ require (
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
+ github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.7.0 // indirect
golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7 // indirect
golang.org/x/text v0.3.3 // indirect
diff --git a/routers/commentsRouter_api.go b/routers/commentsRouter_api.go
index 1efa78f1e3df209ab41a4cde4a1b03e3958491ef..46b76ad258d5102c369c84c9543787005a669a45 100644
--- a/routers/commentsRouter_api.go
+++ b/routers/commentsRouter_api.go
@@ -27,6 +27,17 @@ func init() {
Filters: nil,
Params: nil})
+ beego.GlobalControllerRouter["SimpleDocker/api:ContainerController"] = append(beego.GlobalControllerRouter["SimpleDocker/api:ContainerController"],
+ beego.ControllerComments{
+ Method: "ExportContainer",
+ Router: "/api/container/:containerId/export",
+ AllowHTTPMethods: []string{"get"},
+ MethodParams: param.Make(
+ param.New("containerId", param.InPath),
+ ),
+ Filters: nil,
+ Params: nil})
+
beego.GlobalControllerRouter["SimpleDocker/api:ContainerController"] = append(beego.GlobalControllerRouter["SimpleDocker/api:ContainerController"],
beego.ControllerComments{
Method: "GetContainerInfo",
@@ -183,6 +194,15 @@ func init() {
Filters: nil,
Params: nil})
+ beego.GlobalControllerRouter["SimpleDocker/api:ImageController"] = append(beego.GlobalControllerRouter["SimpleDocker/api:ImageController"],
+ beego.ControllerComments{
+ Method: "ImportImage",
+ Router: "/api/image/import",
+ AllowHTTPMethods: []string{"get"},
+ MethodParams: param.Make(),
+ Filters: nil,
+ Params: nil})
+
beego.GlobalControllerRouter["SimpleDocker/api:ImageController"] = append(beego.GlobalControllerRouter["SimpleDocker/api:ImageController"],
beego.ControllerComments{
Method: "PullImage",
diff --git a/test/api_test.go b/test/api_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..36bde24a34585553ac0bd1d0df414132a473c42c
--- /dev/null
+++ b/test/api_test.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+ "context"
+ "io"
+ "net"
+ "net/http"
+ "os"
+ "testing"
+)
+
+func TestDockerApi(t *testing.T) {
+ httpc := http.Client{
+ Transport: &http.Transport{
+ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
+ return net.Dial("unix", "/var/run/docker.sock")
+ },
+ },
+ }
+
+ var response *http.Response
+ var err error
+
+
+ response, err = httpc.PostForm("http://unix/images/create?fromImage=python:latest", nil)
+
+ if err != nil {
+ panic(err)
+ }
+ io.Copy(os.Stdout, response.Body)
+}
diff --git a/ui/src/main.js b/ui/src/main.js
index a114420417e2164bbe422408844a8e12d62f8350..fc1fae0ccf6f82c22e7e0229fbeb7f803dcd3ef3 100644
--- a/ui/src/main.js
+++ b/ui/src/main.js
@@ -9,7 +9,7 @@ import 'ant-design-vue/dist/antd.css';
import axios from 'axios'
// axios.defaults.baseURL = 'http://10.0.30.78:8081';
-// axios.defaults.baseURL = 'http://localhost:8081';
+axios.defaults.baseURL = 'http://localhost:4050';
Vue.prototype.$lodash = _
Vue.config.productionTip = false
Vue.prototype.$axios = axios
diff --git a/ui/src/views/Image.vue b/ui/src/views/Image.vue
index 45bc71ce4d2effb93adf8c0d554906c674ef4f16..b8fafda771a8b7906583f9846416a1f755ddf6e5 100644
--- a/ui/src/views/Image.vue
+++ b/ui/src/views/Image.vue
@@ -20,6 +20,12 @@
拉取
+
+
+
+ 导入
+
+
@@ -27,16 +33,15 @@
style="margin-top: 30px">
- 详情
+ 运行
删除
- e.preventDefault()">更多
+ e.preventDefault()">更多
- 运行镜像
+ 镜像详情
导出镜像
@@ -44,6 +49,9 @@
重新标记
+
+ 推送镜像
+
@@ -90,6 +98,24 @@
+
+
+
+
+
+ 导入镜像
+
+
+
+
+
+
+
+
+
+
+ 无
+ 授权信息1
+ 授权信息2
+ 授权信息3
+ 授权信息4
+ 授权信息5
+
+
{{pullLog}}
@@ -136,7 +173,7 @@
import {mapActions} from "vuex";
import imageApi from "../api/ImageApi";
- import {guid} from '../utils/index'
+ import {guid, download} from '../utils/index'
const columns = [
{
@@ -185,9 +222,10 @@
env: '',
volume: ''
},
- pullImageConfig: {imageName: ''},
+ pullImageConfig: {imageName: '', auth: ''},
pullLog: '',
pulling: false,
+ importImageVisible: false, // 导入Image的Modal
pullImageVisible: false,
runImageVisible: false,
tagImageVisible: false,
@@ -325,7 +363,7 @@
}
this.$axios.create(config).get(`/api/image/${imageId}/save`, {responseType: 'blob'}).then(
(res) => {
- this.download(res.data, `${imageId}.tar.gz`)
+ download(res.data, `${imageId}.tar.gz`)
this.$message.info({content: "镜像已成功导出并下载....", key});
})
.catch(e => {
@@ -343,24 +381,6 @@
} else {
this.$message.error(Msg);
}
- },
- download: function (data, fileName) {
- if (window.navigator && window.navigator.msSaveOrOpenBlob) {
- let blob = new Blob([data], {
- type: 'application/vnd.ms-excel'
- })
- window.navigator.msSaveOrOpenBlob(blob, fileName)
- } else {
- let blob = new Blob([data])
- let downloadElement = document.createElement('a')
- let href = window.URL.createObjectURL(blob)
- downloadElement.href = href
- downloadElement.download = fileName
- document.body.appendChild(downloadElement)
- downloadElement.click()
- document.body.removeChild(downloadElement)
- window.URL.revokeObjectURL(href)
- }
}, callReTagApi: function () {
if (this.newTag === '' || this.newTag.trim() === '') {
this.tagImageVisible = false
@@ -370,7 +390,6 @@
});
return
}
-
if (this.newTag.trim() === this.oldTag.trim()) {
this.tagImageVisible = false
this.$notification['warning']({
@@ -406,6 +425,8 @@
description: "镜像标记失败,请检查 Docker 服务是否正常"
});
})
+ }, callImportImageApi() {
+
}
}
}