2018-03-14 14:12:26 +08:00
|
|
|
|
// Copyright (C) 2014-2018 Goodrain Co., Ltd.
|
2018-01-10 13:35:05 +08:00
|
|
|
|
// RAINBOND, Application Management Platform
|
2018-03-14 14:33:31 +08:00
|
|
|
|
|
2018-01-10 13:35:05 +08:00
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
// (at your option) any later version. For any non-GPL usage of Rainbond,
|
|
|
|
|
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
|
|
|
|
|
// must be obtained first.
|
2018-03-14 14:33:31 +08:00
|
|
|
|
|
2018-01-10 13:35:05 +08:00
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
// GNU General Public License for more details.
|
2018-03-14 14:33:31 +08:00
|
|
|
|
|
2018-01-10 13:35:05 +08:00
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
package sources
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bufio"
|
2018-03-01 13:34:54 +08:00
|
|
|
|
"encoding/base64"
|
|
|
|
|
"encoding/json"
|
2018-01-10 13:35:05 +08:00
|
|
|
|
"fmt"
|
2018-05-08 15:12:48 +08:00
|
|
|
|
"io"
|
2018-03-28 19:34:35 +08:00
|
|
|
|
"os"
|
2018-05-08 15:12:48 +08:00
|
|
|
|
"path"
|
|
|
|
|
"path/filepath"
|
2018-01-10 13:35:05 +08:00
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
2018-02-28 15:14:27 +08:00
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
|
|
|
|
2018-01-10 13:35:05 +08:00
|
|
|
|
"github.com/docker/distribution/reference"
|
2018-02-28 15:14:27 +08:00
|
|
|
|
"golang.org/x/net/context"
|
2018-09-10 17:33:16 +08:00
|
|
|
|
|
2018-02-07 11:36:24 +08:00
|
|
|
|
//"github.com/docker/docker/api/types"
|
2018-12-07 14:24:14 +08:00
|
|
|
|
"github.com/docker/docker/api/types"
|
2018-02-07 11:28:31 +08:00
|
|
|
|
//"github.com/docker/docker/client"
|
2018-12-07 14:24:14 +08:00
|
|
|
|
"github.com/docker/docker/client"
|
2018-03-01 18:30:24 +08:00
|
|
|
|
"github.com/docker/docker/pkg/archive"
|
2019-05-31 10:52:47 +08:00
|
|
|
|
"github.com/goodrain/rainbond/builder"
|
2018-04-24 16:44:59 +08:00
|
|
|
|
"github.com/goodrain/rainbond/builder/model"
|
|
|
|
|
"github.com/goodrain/rainbond/event"
|
2018-01-10 13:35:05 +08:00
|
|
|
|
)
|
|
|
|
|
|
2019-05-31 10:52:47 +08:00
|
|
|
|
//ErrorNoAuth error no auth
|
|
|
|
|
var ErrorNoAuth = fmt.Errorf("pull image require docker login")
|
|
|
|
|
|
|
|
|
|
//ErrorNoImage error no image
|
|
|
|
|
var ErrorNoImage = fmt.Errorf("image not exist")
|
|
|
|
|
|
2018-01-10 13:35:05 +08:00
|
|
|
|
//ImagePull 拉取镜像
|
|
|
|
|
//timeout 分钟为单位
|
2018-05-25 18:24:17 +08:00
|
|
|
|
func ImagePull(dockerCli *client.Client, image string, username, password string, logger event.Logger, timeout int) (*types.ImageInspect, error) {
|
2018-01-10 13:35:05 +08:00
|
|
|
|
if logger != nil {
|
|
|
|
|
//进度信息
|
|
|
|
|
logger.Info(fmt.Sprintf("开始获取镜像:%s", image), map[string]string{"step": "pullimage"})
|
|
|
|
|
}
|
2018-05-25 18:24:17 +08:00
|
|
|
|
var pullipo types.ImagePullOptions
|
|
|
|
|
if username != "" && password != "" {
|
|
|
|
|
auth, err := EncodeAuthToBase64(types.AuthConfig{Username: username, Password: password})
|
|
|
|
|
if err != nil {
|
|
|
|
|
logrus.Errorf("make auth base63 push image error: %s", err.Error())
|
|
|
|
|
logger.Error(fmt.Sprintf("生成获取镜像的Token失败"), map[string]string{"step": "builder-exector", "status": "failure"})
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
pullipo = types.ImagePullOptions{
|
|
|
|
|
RegistryAuth: auth,
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
pullipo = types.ImagePullOptions{}
|
|
|
|
|
}
|
2018-02-07 14:50:15 +08:00
|
|
|
|
rf, err := reference.ParseAnyReference(image)
|
2018-01-10 13:35:05 +08:00
|
|
|
|
if err != nil {
|
2018-02-07 14:50:15 +08:00
|
|
|
|
logrus.Errorf("reference image error: %s", err.Error())
|
2018-01-10 13:35:05 +08:00
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
//最少一分钟
|
|
|
|
|
if timeout < 1 {
|
|
|
|
|
timeout = 1
|
|
|
|
|
}
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*time.Duration(timeout))
|
|
|
|
|
defer cancel()
|
2018-02-07 14:50:15 +08:00
|
|
|
|
//TODO: 使用1.12版本api的bug “repository name must be canonical”,使用rf.String()完整的镜像地址
|
2018-05-25 18:24:17 +08:00
|
|
|
|
readcloser, err := dockerCli.ImagePull(ctx, rf.String(), pullipo)
|
2019-05-31 10:52:47 +08:00
|
|
|
|
logger.Info(fmt.Sprintf("Success Pull Image:%s", image), map[string]string{"step": "pullimage"})
|
2018-01-10 13:35:05 +08:00
|
|
|
|
if err != nil {
|
2018-02-07 14:50:15 +08:00
|
|
|
|
logrus.Debugf("image name: %s readcloser error: %v", image, err.Error())
|
2018-01-10 13:35:05 +08:00
|
|
|
|
if strings.HasSuffix(err.Error(), "does not exist or no pull access") {
|
2018-04-20 14:26:54 +08:00
|
|
|
|
if logger != nil {
|
|
|
|
|
logger.Error(fmt.Sprintf("镜像:%s不存在或无权获取", image), map[string]string{"step": "pullimage"})
|
|
|
|
|
}
|
2018-01-10 13:35:05 +08:00
|
|
|
|
return nil, fmt.Errorf("Image(%s) does not exist or no pull access", image)
|
|
|
|
|
}
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
defer readcloser.Close()
|
2018-05-28 18:19:30 +08:00
|
|
|
|
dec := json.NewDecoder(readcloser)
|
2018-01-10 13:35:05 +08:00
|
|
|
|
for {
|
2018-05-28 18:19:30 +08:00
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return nil, ctx.Err()
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
var jm JSONMessage
|
|
|
|
|
if err := dec.Decode(&jm); err != nil {
|
|
|
|
|
if err == io.EOF {
|
|
|
|
|
break
|
2018-01-10 13:35:05 +08:00
|
|
|
|
}
|
2018-12-07 11:52:48 +08:00
|
|
|
|
logrus.Debugf("error decoding jm(JSONMessage): %v", err)
|
2018-05-28 18:19:30 +08:00
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if jm.Error != nil {
|
2018-12-07 11:52:48 +08:00
|
|
|
|
logrus.Debugf("error pulling image: %v", jm.Error)
|
2018-05-28 18:19:30 +08:00
|
|
|
|
return nil, jm.Error
|
2018-01-10 13:35:05 +08:00
|
|
|
|
}
|
2018-05-28 19:37:04 +08:00
|
|
|
|
if logger != nil {
|
2018-12-06 12:45:00 +08:00
|
|
|
|
logger.Debug(fmt.Sprintf(jm.JSONString()), map[string]string{"step": "progress"})
|
2018-05-28 19:37:04 +08:00
|
|
|
|
} else {
|
|
|
|
|
fmt.Println(jm.JSONString())
|
|
|
|
|
}
|
2018-01-10 13:35:05 +08:00
|
|
|
|
}
|
2018-12-05 16:36:51 +08:00
|
|
|
|
logger.Debug("Get the image information and its raw representation", map[string]string{"step": "progress"})
|
2018-11-05 20:34:24 +08:00
|
|
|
|
ins, _, err := dockerCli.ImageInspectWithRaw(ctx, image)
|
2018-01-10 13:35:05 +08:00
|
|
|
|
if err != nil {
|
2018-12-05 16:36:51 +08:00
|
|
|
|
logger.Debug("Fail to get the image information and its raw representation", map[string]string{"step": "progress"})
|
2018-01-10 13:35:05 +08:00
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &ins, nil
|
|
|
|
|
}
|
2018-01-17 22:06:58 +08:00
|
|
|
|
|
2018-01-25 22:10:34 +08:00
|
|
|
|
//ImageTag 修改镜像tag
|
|
|
|
|
func ImageTag(dockerCli *client.Client, source, target string, logger event.Logger, timeout int) error {
|
|
|
|
|
if logger != nil {
|
|
|
|
|
//进度信息
|
|
|
|
|
logger.Info(fmt.Sprintf("开始修改镜像tag:%s -> %s", source, target), map[string]string{"step": "changetag"})
|
|
|
|
|
}
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*time.Duration(timeout))
|
|
|
|
|
defer cancel()
|
|
|
|
|
err := dockerCli.ImageTag(ctx, source, target)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logrus.Debugf("image tag err: %s", err.Error())
|
|
|
|
|
return err
|
|
|
|
|
}
|
2018-02-28 15:14:27 +08:00
|
|
|
|
logger.Info("镜像tag修改完成", map[string]string{"step": "changetag"})
|
2018-01-25 22:10:34 +08:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//ImageNameHandle 解析imagename
|
|
|
|
|
func ImageNameHandle(imageName string) *model.ImageName {
|
|
|
|
|
var i model.ImageName
|
2018-02-28 15:14:27 +08:00
|
|
|
|
if strings.Contains(imageName, "/") {
|
2018-01-25 22:10:34 +08:00
|
|
|
|
mm := strings.Split(imageName, "/")
|
|
|
|
|
i.Host = mm[0]
|
|
|
|
|
names := strings.Join(mm[1:], "/")
|
2018-02-28 15:14:27 +08:00
|
|
|
|
if strings.Contains(names, ":") {
|
2018-01-25 22:10:34 +08:00
|
|
|
|
nn := strings.Split(names, ":")
|
|
|
|
|
i.Name = nn[0]
|
2018-09-10 17:33:16 +08:00
|
|
|
|
i.Tag = nn[1]
|
|
|
|
|
} else {
|
|
|
|
|
i.Name = names
|
|
|
|
|
i.Tag = "latest"
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if strings.Contains(imageName, ":") {
|
|
|
|
|
nn := strings.Split(imageName, ":")
|
|
|
|
|
i.Name = nn[0]
|
|
|
|
|
i.Tag = nn[1]
|
|
|
|
|
} else {
|
|
|
|
|
i.Name = imageName
|
|
|
|
|
i.Tag = "latest"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return &i
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//ImageNameWithNamespaceHandle if have namespace,will parse namespace
|
|
|
|
|
func ImageNameWithNamespaceHandle(imageName string) *model.ImageName {
|
|
|
|
|
var i model.ImageName
|
|
|
|
|
if strings.Contains(imageName, "/") {
|
|
|
|
|
mm := strings.Split(imageName, "/")
|
|
|
|
|
i.Host = mm[0]
|
|
|
|
|
names := strings.Join(mm[1:], "/")
|
|
|
|
|
if len(mm) >= 3 {
|
|
|
|
|
i.Namespace = mm[1]
|
|
|
|
|
names = strings.Join(mm[2:], "/")
|
|
|
|
|
}
|
|
|
|
|
if strings.Contains(names, ":") {
|
|
|
|
|
nn := strings.Split(names, ":")
|
|
|
|
|
i.Name = nn[0]
|
2018-01-25 22:10:34 +08:00
|
|
|
|
i.Tag = nn[1]
|
2018-02-28 15:14:27 +08:00
|
|
|
|
} else {
|
2018-01-25 22:10:34 +08:00
|
|
|
|
i.Name = names
|
|
|
|
|
i.Tag = "latest"
|
|
|
|
|
}
|
2018-02-28 15:14:27 +08:00
|
|
|
|
} else {
|
|
|
|
|
if strings.Contains(imageName, ":") {
|
2018-01-25 22:10:34 +08:00
|
|
|
|
nn := strings.Split(imageName, ":")
|
|
|
|
|
i.Name = nn[0]
|
|
|
|
|
i.Tag = nn[1]
|
2018-02-28 15:14:27 +08:00
|
|
|
|
} else {
|
2018-01-25 22:10:34 +08:00
|
|
|
|
i.Name = imageName
|
|
|
|
|
i.Tag = "latest"
|
2018-02-28 15:14:27 +08:00
|
|
|
|
}
|
2018-01-25 22:10:34 +08:00
|
|
|
|
}
|
|
|
|
|
return &i
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-17 23:31:26 +08:00
|
|
|
|
// GenSaveImageName generates the final name of the image, which is the name of
|
|
|
|
|
// the image in the exported tar package.
|
|
|
|
|
func GenSaveImageName(name string) string {
|
|
|
|
|
imageName := ImageNameWithNamespaceHandle(name)
|
|
|
|
|
return fmt.Sprintf("%s/%s:%s", builder.REGISTRYDOMAIN, imageName.Name, imageName.Tag)
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-17 22:06:58 +08:00
|
|
|
|
//ImagePush 推送镜像
|
|
|
|
|
//timeout 分钟为单位
|
2018-04-03 11:54:29 +08:00
|
|
|
|
func ImagePush(dockerCli *client.Client, image, user, pass string, logger event.Logger, timeout int) error {
|
2018-01-25 22:10:34 +08:00
|
|
|
|
if logger != nil {
|
|
|
|
|
//进度信息
|
|
|
|
|
logger.Info(fmt.Sprintf("开始推送镜像:%s", image), map[string]string{"step": "pushimage"})
|
|
|
|
|
}
|
|
|
|
|
//最少一分钟
|
|
|
|
|
if timeout < 1 {
|
|
|
|
|
timeout = 1
|
|
|
|
|
}
|
2018-04-03 11:54:29 +08:00
|
|
|
|
if user == "" {
|
|
|
|
|
user = os.Getenv("LOCAL_HUB_USER")
|
|
|
|
|
}
|
|
|
|
|
if pass == "" {
|
|
|
|
|
pass = os.Getenv("LOCAL_HUB_PASS")
|
|
|
|
|
}
|
|
|
|
|
ref, err := reference.ParseNormalizedNamed(image)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
var opts types.ImagePushOptions
|
|
|
|
|
pushauth, err := EncodeAuthToBase64(types.AuthConfig{
|
|
|
|
|
Username: user,
|
|
|
|
|
Password: pass,
|
|
|
|
|
ServerAddress: reference.Domain(ref),
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
logrus.Errorf("make auth base63 push image error: %s", err.Error())
|
|
|
|
|
if logger != nil {
|
|
|
|
|
logger.Error(fmt.Sprintf("生成获取镜像的Token失败"), map[string]string{"step": "builder-exector", "status": "failure"})
|
2018-03-28 19:34:35 +08:00
|
|
|
|
}
|
2018-04-03 11:54:29 +08:00
|
|
|
|
return err
|
2018-03-28 19:34:35 +08:00
|
|
|
|
}
|
2018-04-03 11:54:29 +08:00
|
|
|
|
opts.RegistryAuth = pushauth
|
2018-01-25 22:10:34 +08:00
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*time.Duration(timeout))
|
|
|
|
|
defer cancel()
|
|
|
|
|
readcloser, err := dockerCli.ImagePush(ctx, image, opts)
|
|
|
|
|
if err != nil {
|
2018-03-07 15:11:20 +08:00
|
|
|
|
if strings.Contains(err.Error(), "does not exist") {
|
2018-03-01 13:34:54 +08:00
|
|
|
|
if logger != nil {
|
|
|
|
|
logger.Error(fmt.Sprintf("镜像:%s不存在,不能推送", image), map[string]string{"step": "pushimage"})
|
|
|
|
|
}
|
2018-01-25 22:10:34 +08:00
|
|
|
|
return fmt.Errorf("Image(%s) does not exist", image)
|
|
|
|
|
}
|
|
|
|
|
return err
|
2018-02-28 15:14:27 +08:00
|
|
|
|
}
|
2018-12-06 12:45:00 +08:00
|
|
|
|
logger.Info(fmt.Sprintf("成功推送镜像:%s", image), map[string]string{"step": "pushimage"})
|
2018-03-01 13:34:54 +08:00
|
|
|
|
if readcloser != nil {
|
|
|
|
|
defer readcloser.Close()
|
2018-05-28 18:19:30 +08:00
|
|
|
|
dec := json.NewDecoder(readcloser)
|
2018-03-01 13:34:54 +08:00
|
|
|
|
for {
|
2018-03-22 16:32:07 +08:00
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return ctx.Err()
|
|
|
|
|
default:
|
|
|
|
|
}
|
2018-05-28 18:19:30 +08:00
|
|
|
|
var jm JSONMessage
|
|
|
|
|
if err := dec.Decode(&jm); err != nil {
|
|
|
|
|
if err == io.EOF {
|
|
|
|
|
break
|
2018-03-22 16:32:07 +08:00
|
|
|
|
}
|
|
|
|
|
return err
|
2018-01-25 22:10:34 +08:00
|
|
|
|
}
|
2018-05-28 18:19:30 +08:00
|
|
|
|
if jm.Error != nil {
|
|
|
|
|
return jm.Error
|
|
|
|
|
}
|
|
|
|
|
logger.Debug(jm.JSONString(), map[string]string{"step": "progress"})
|
2018-01-25 22:10:34 +08:00
|
|
|
|
}
|
2018-03-28 19:14:27 +08:00
|
|
|
|
|
2018-02-28 15:14:27 +08:00
|
|
|
|
}
|
2018-01-17 22:06:58 +08:00
|
|
|
|
return nil
|
|
|
|
|
}
|
2018-01-29 11:25:49 +08:00
|
|
|
|
|
2018-04-03 11:54:29 +08:00
|
|
|
|
//TrustedImagePush push image to trusted registry
|
|
|
|
|
func TrustedImagePush(dockerCli *client.Client, image, user, pass string, logger event.Logger, timeout int) error {
|
|
|
|
|
if err := CheckTrustedRepositories(image, user, pass); err != nil {
|
|
|
|
|
return err
|
2018-03-01 13:34:54 +08:00
|
|
|
|
}
|
2018-04-03 11:54:29 +08:00
|
|
|
|
return ImagePush(dockerCli, image, user, pass, logger, timeout)
|
2018-03-01 13:34:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-04-03 11:54:29 +08:00
|
|
|
|
//CheckTrustedRepositories check Repositories is exist ,if not create it.
|
|
|
|
|
func CheckTrustedRepositories(image, user, pass string) error {
|
|
|
|
|
ref, err := reference.ParseNormalizedNamed(image)
|
2018-03-01 13:34:54 +08:00
|
|
|
|
if err != nil {
|
2018-04-03 11:54:29 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
var server string
|
|
|
|
|
if reference.IsNameOnly(ref) {
|
|
|
|
|
server = "docker.io"
|
|
|
|
|
} else {
|
|
|
|
|
server = reference.Domain(ref)
|
|
|
|
|
}
|
|
|
|
|
cli, err := createTrustedRegistryClient(server, user, pass)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
var namespace, repName string
|
|
|
|
|
infos := strings.Split(reference.TrimNamed(ref).String(), "/")
|
|
|
|
|
if len(infos) == 3 && infos[0] == server {
|
|
|
|
|
namespace = infos[1]
|
|
|
|
|
repName = infos[2]
|
|
|
|
|
}
|
|
|
|
|
if len(infos) == 2 {
|
|
|
|
|
namespace = infos[0]
|
|
|
|
|
repName = infos[1]
|
2018-03-01 13:34:54 +08:00
|
|
|
|
}
|
2018-04-03 11:54:29 +08:00
|
|
|
|
_, err = cli.GetRepository(namespace, repName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if err.Error() == "resource does not exist" {
|
|
|
|
|
rep := Repostory{
|
|
|
|
|
Name: repName,
|
2019-02-25 15:08:57 +08:00
|
|
|
|
ShortDescription: image, // The maximum length is 140
|
2018-04-03 11:54:29 +08:00
|
|
|
|
LongDescription: fmt.Sprintf("push image for %s", image),
|
|
|
|
|
Visibility: "private",
|
|
|
|
|
}
|
2019-02-25 15:08:57 +08:00
|
|
|
|
if len(rep.ShortDescription) > 140 {
|
|
|
|
|
rep.ShortDescription = rep.ShortDescription[0:140]
|
|
|
|
|
}
|
2018-04-03 11:54:29 +08:00
|
|
|
|
err := cli.CreateRepository(namespace, &rep)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("create repostory error,%s", err.Error())
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("get repostory error,%s", err.Error())
|
2018-03-01 13:34:54 +08:00
|
|
|
|
}
|
2018-04-03 11:54:29 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|
2018-03-01 13:34:54 +08:00
|
|
|
|
|
2018-04-03 11:54:29 +08:00
|
|
|
|
// EncodeAuthToBase64 serializes the auth configuration as JSON base64 payload
|
|
|
|
|
func EncodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
|
|
|
|
|
buf, err := json.Marshal(authConfig)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return base64.URLEncoding.EncodeToString(buf), nil
|
2018-03-01 13:34:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-29 11:25:49 +08:00
|
|
|
|
//ImageBuild ImageBuild
|
2018-03-01 18:30:24 +08:00
|
|
|
|
func ImageBuild(dockerCli *client.Client, contextDir string, options types.ImageBuildOptions, logger event.Logger, timeout int) error {
|
2019-06-29 15:20:11 +08:00
|
|
|
|
var ctx context.Context
|
|
|
|
|
if timeout != 0 {
|
|
|
|
|
var cancel context.CancelFunc
|
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), time.Minute*time.Duration(timeout))
|
|
|
|
|
defer cancel()
|
|
|
|
|
} else {
|
|
|
|
|
ctx = context.Background()
|
|
|
|
|
}
|
2018-03-01 18:30:24 +08:00
|
|
|
|
buildCtx, err := archive.TarWithOptions(contextDir, &archive.TarOptions{
|
|
|
|
|
Compression: archive.Uncompressed,
|
|
|
|
|
ExcludePatterns: []string{""},
|
|
|
|
|
IncludeFiles: []string{"."},
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2018-03-01 18:57:43 +08:00
|
|
|
|
rc, err := dockerCli.ImageBuild(ctx, buildCtx, options)
|
2018-01-29 11:25:49 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2018-03-07 15:11:20 +08:00
|
|
|
|
if rc.Body != nil {
|
|
|
|
|
defer rc.Body.Close()
|
2018-05-28 18:19:30 +08:00
|
|
|
|
dec := json.NewDecoder(rc.Body)
|
2018-03-07 15:11:20 +08:00
|
|
|
|
for {
|
2018-03-22 16:32:07 +08:00
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return ctx.Err()
|
|
|
|
|
default:
|
|
|
|
|
}
|
2018-05-28 18:19:30 +08:00
|
|
|
|
var jm JSONMessage
|
|
|
|
|
if err := dec.Decode(&jm); err != nil {
|
|
|
|
|
if err == io.EOF {
|
|
|
|
|
break
|
2018-03-22 16:32:07 +08:00
|
|
|
|
}
|
|
|
|
|
return err
|
2018-01-29 11:25:49 +08:00
|
|
|
|
}
|
2018-05-28 18:19:30 +08:00
|
|
|
|
if jm.Error != nil {
|
|
|
|
|
return jm.Error
|
|
|
|
|
}
|
2018-07-11 18:33:19 +08:00
|
|
|
|
logger.Info(jm.JSONString(), map[string]string{"step": "build-progress"})
|
2018-01-29 11:25:49 +08:00
|
|
|
|
}
|
2018-02-28 15:14:27 +08:00
|
|
|
|
}
|
2018-01-29 11:25:49 +08:00
|
|
|
|
return nil
|
2018-02-28 15:14:27 +08:00
|
|
|
|
}
|
2018-03-23 12:34:32 +08:00
|
|
|
|
|
|
|
|
|
//ImageInspectWithRaw get image inspect
|
|
|
|
|
func ImageInspectWithRaw(dockerCli *client.Client, image string) (*types.ImageInspect, error) {
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
defer cancel()
|
2018-11-05 20:34:24 +08:00
|
|
|
|
ins, _, err := dockerCli.ImageInspectWithRaw(ctx, image)
|
2018-03-23 12:34:32 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &ins, nil
|
|
|
|
|
}
|
2018-05-08 15:12:48 +08:00
|
|
|
|
|
|
|
|
|
//ImageSave save image to tar file
|
|
|
|
|
// destination destination file name eg. /tmp/xxx.tar
|
2018-05-08 15:38:29 +08:00
|
|
|
|
func ImageSave(dockerCli *client.Client, image, destination string, logger event.Logger) error {
|
2018-05-08 15:12:48 +08:00
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
defer cancel()
|
|
|
|
|
rc, err := dockerCli.ImageSave(ctx, []string{image})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2018-05-08 15:38:29 +08:00
|
|
|
|
defer rc.Close()
|
2018-05-08 15:12:48 +08:00
|
|
|
|
return CopyToFile(destination, rc)
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-10 18:42:03 +08:00
|
|
|
|
//ImageLoad load image from tar file
|
2018-05-17 11:27:04 +08:00
|
|
|
|
// destination destination file name eg. /tmp/xxx.tar
|
|
|
|
|
func ImageLoad(dockerCli *client.Client, tarFile string, logger event.Logger) error {
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
reader, err := os.OpenFile(tarFile, os.O_RDONLY, 0644)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer reader.Close()
|
|
|
|
|
|
2018-09-10 18:42:03 +08:00
|
|
|
|
rc, err := dockerCli.ImageLoad(ctx, reader, false)
|
2018-05-17 11:27:04 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2018-09-10 18:42:03 +08:00
|
|
|
|
if rc.Body != nil {
|
|
|
|
|
defer rc.Body.Close()
|
|
|
|
|
dec := json.NewDecoder(rc.Body)
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return ctx.Err()
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
var jm JSONMessage
|
|
|
|
|
if err := dec.Decode(&jm); err != nil {
|
|
|
|
|
if err == io.EOF {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if jm.Error != nil {
|
|
|
|
|
return jm.Error
|
|
|
|
|
}
|
|
|
|
|
logger.Info(jm.JSONString(), map[string]string{"step": "build-progress"})
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-05-17 11:27:04 +08:00
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-08 15:38:29 +08:00
|
|
|
|
//ImageImport save image to tar file
|
|
|
|
|
// source source file name eg. /tmp/xxx.tar
|
|
|
|
|
func ImageImport(dockerCli *client.Client, image, source string, logger event.Logger) error {
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
file, err := os.Open(source)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
|
|
isource := types.ImageImportSource{
|
|
|
|
|
Source: file,
|
|
|
|
|
SourceName: "-",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
options := types.ImageImportOptions{}
|
|
|
|
|
|
|
|
|
|
readcloser, err := dockerCli.ImageImport(ctx, isource, image, options)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if readcloser != nil {
|
|
|
|
|
defer readcloser.Close()
|
|
|
|
|
r := bufio.NewReader(readcloser)
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return ctx.Err()
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
if line, _, err := r.ReadLine(); err == nil {
|
|
|
|
|
if logger != nil {
|
|
|
|
|
logger.Debug(string(line), map[string]string{"step": "progress"})
|
|
|
|
|
} else {
|
|
|
|
|
fmt.Println(string(line))
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if err.Error() == "EOF" {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-08 15:12:48 +08:00
|
|
|
|
// CopyToFile writes the content of the reader to the specified file
|
|
|
|
|
func CopyToFile(outfile string, r io.Reader) error {
|
|
|
|
|
// We use sequential file access here to avoid depleting the standby list
|
|
|
|
|
// on Windows. On Linux, this is a call directly to ioutil.TempFile
|
2018-10-15 21:03:39 +08:00
|
|
|
|
tmpFile, err := os.OpenFile(path.Join(filepath.Dir(outfile), ".docker_temp_"), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
|
2018-05-08 15:12:48 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2018-09-14 14:35:30 +08:00
|
|
|
|
defer tmpFile.Close()
|
2018-05-08 15:12:48 +08:00
|
|
|
|
tmpPath := tmpFile.Name()
|
|
|
|
|
_, err = io.Copy(tmpFile, r)
|
|
|
|
|
if err != nil {
|
|
|
|
|
os.Remove(tmpPath)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err = os.Rename(tmpPath, outfile); err != nil {
|
|
|
|
|
os.Remove(tmpPath)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2018-05-28 12:18:29 +08:00
|
|
|
|
|
|
|
|
|
//ImageRemove remove image
|
|
|
|
|
func ImageRemove(dockerCli *client.Client, image string) error {
|
2019-06-29 15:20:11 +08:00
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
2018-05-28 12:18:29 +08:00
|
|
|
|
defer cancel()
|
2019-06-29 15:20:11 +08:00
|
|
|
|
_, err := dockerCli.ImageRemove(ctx, image, types.ImageRemoveOptions{Force: true})
|
2018-05-28 12:18:29 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|