使用golang实现的简单镜像工具推送到ecr私有仓库

通过golang实现一个简单的镜像下载工具,用来解决中国区无法使用常用镜像的问题

总体步骤

  • 启动一台海外区域的ec2实例
  • 安装docker和awscli
  • 配置凭证访问国内ecr仓库
  • 编写web服务实现镜像转换和自动推送

以上步骤也可以采用ecs特权容器的方式托管,本文中使用ec2完成

配置环境

sudo yum install docker -y
sudo systemctl start docker
sudo systemctl enable docker
sudo groupadd docker
sudo usermod -aG docker ec2-user

编写web服务

前端搜索

直接用chatgpt生成前端

在这里插入图片描述

<!DOCTYPE html>
<html>

<head>
    <title>ToImage</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f8f8f8;
            margin: 0;
            padding: 0;
        }

        .container {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            flex-direction: column;
        }

        .title {
            font-size: 48px;
            font-weight: bold;
            margin-bottom: 30px;
            color: #4CAF50;
            text-shadow: 2px 2px 5px #ccc;
        }

        .parent {
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.1);
            background-color: #fff;
            width: 80%;
            max-width: 600px;
        }

        .search {
            width: 100%;
            padding: 10px;
            font-size: 24px;
            border: 2px solid #ccc;
            border-radius: 10px;
            outline: none;
            margin-bottom: 20px;
        }

        .btn {
            width: 100%;
            padding: 10px;
            font-size: 24px;
            border: none;
            border-radius: 10px;
            background-color: #4CAF50;
            color: #fff;
            cursor: pointer;
            transition: all 0.3s ease;
            text-shadow: 2px 2px 5px #ccc;
        }

        .btn:hover {
            background-color: #3e8e41;
        }

        @media screen and (max-width: 600px) {
            .title {
                font-size: 36px;
            }

            .search {
                font-size: 18px;
            }

            .btn {
                font-size: 18px;
            }
        }
    </style>
</head>

<body>
    <div class="container">
        <div class="title">ToImage</div>
        <form class="parent" method="get" action="/upload"> <input type="text" class="search"
                placeholder="Please Input..." name="iamgename" /> <input type="submit" class="btn" value="Search" />
        </form>
    </div>
</body>

</html>

前端输出

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Toimage</title>
    <style>
        /* 样式表 */
        .copy-btn {
            background-color: #4CAF50;
            color: white;
            border: none;
            padding: 5px;
            text-align: center;
            text-decoration: none;
            display: block; /* 修改按钮为块级元素 */
            font-size: 16px;
            font-weight: bold; /* 加粗字体 */
            margin-top: 10px; /* 调整按钮与文字之间的距离 */
            cursor: pointer;
        }
        .copy-btn:hover {
            background-color: #3e8e41;
        }
        /* 居中显示 */
        .container {
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            height: 100vh;
        }
        /* 加粗字体 */
        p {
            font-weight: bold;
        }
    </style>
    <script>
        /* 复制文本函数 */
        function copyText(text) {
            var textarea = document.createElement('textarea');
            textarea.textContent = text;
            document.body.appendChild(textarea);

            textarea.select();
            document.execCommand('copy');

            document.body.removeChild(textarea);
        }
    </script>
</head>
<body>
    <div class="container">
        <p>{{.ImageName}}</p>
        <button class="copy-btn" onclick="copyText('{{.ImageName}}')">复制</button> <!-- 将按钮放在段落下面 -->
        <p>{{.Command}}</p>
        <button class="copy-btn" onclick="copyText('{{.Command}}')">复制</button> <!-- 将按钮放在段落下面 -->
    </div>
</body>
</html>

后端服务

  • 使用docker的goclient实现pull,tag和push image
  • 使用aws-go-sdk完成ecr认证
  • 在环境中配置aksk(需要ecr相关权限),自动获取对应的账户id,默认为北京区仓库
package main

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"os"
	"strings"

	"log"
	"net/http"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ecr"
	"github.com/aws/aws-sdk-go/service/sts"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/client"
)

type Output struct {
	ImageName string
	Command   string
}

func main() {
	http.HandleFunc("/", index)
	http.HandleFunc("/upload", myrepo)
	log.Println("The server is listening on 0.0.0.0:5455")
	http.ListenAndServe(":5455", nil)
}

// index
func index(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("/home/ec2-user/toimage/index.html")
	t.Execute(w, "hi")
}

// toimage
func myrepo(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("/home/ec2-user/toimage/output.html")
	log.Println("recieving request")
	imagename := r.URL.Query().Get("iamgename")
	if imagename == "" {
		log.Println("Please input image name and tag")
		return
	}

	// Get account id
	sts_client := sts.New(session.New(), aws.NewConfig().WithRegion("cn-north-1"))
	input := &sts.GetCallerIdentityInput{}
	stsresult, err := sts_client.GetCallerIdentity(input)
	if err != nil {
		log.Println("Can not get account id")
		// log.Println("Can not get account id")
		return
	}
	accountid := *stsresult.Account
	log.Println("Your destination account is", accountid)

	// Initialize
	ctx := context.Background()
	cli, err := client.NewClientWithOpts(client.WithVersion("1.41"))
	if err != nil {
		log.Println("Unable to create docker client")
		return
	}

	// list images
	// images, err := cli.ImageList(ctx, types.ImageListOptions{})
	// if err != nil {
	// 	log.Println("Unable to list images")
	// 	return
	// }

	// for _, image := range images {
	// 	if len(image.RepoTags) > 0 && imagename == image.RepoTags[0] {
	// 		log.Printf("The image '%s' already exists on the host!", imagename)
	// 		return
	// 	}
	// }

	// pull image
	pullReader, pull_err := cli.ImagePull(ctx, imagename, types.ImagePullOptions{})
	if pull_err != nil {
		log.Printf("Failed to pull image '%s': %s\n", imagename, err.Error())
		return
	}
	defer pullReader.Close()
	io.Copy(os.Stdout, pullReader)
	log.Printf("Successcully pull image %s", imagename)

	//tag image
	parts := strings.Split(imagename, "/")
	tempstr := parts[len(parts)-1]
	split := strings.Split(tempstr, ":")
	shortname := split[0]
	tag := ""
	if len(split) == 2 {
		tag = split[1]
	} else {
		tag = "latest"
	}
	tagimage := accountid + ".dkr.ecr.cn-north-1.amazonaws.com.cn/" + shortname + ":" + tag
	log.Println("The image name is", tagimage)

	log.Println("The image name is", tagimage)
	log.Println("login with")
	command := fmt.Sprintf("aws ecr get-login-password --region cn-north-1 | docker login --username AWS --password-stdin %s.dkr.ecr.cn-north-1.amazonaws.com.cn", accountid)
	log.Println(command)
	output := Output{
		ImageName: tagimage,
		Command:   command,
	}
	t.Execute(w, output)

	cli.ImageTag(ctx, imagename, tagimage)

	// Create repo
	mySession := session.Must(session.NewSession())
	ecr_cli := ecr.New(mySession, aws.NewConfig().WithRegion("cn-north-1"))
	create_input := &ecr.CreateRepositoryInput{
		RepositoryName: aws.String(shortname),
	}
	ecrresult, err := ecr_cli.CreateRepository(create_input)
	if err != nil {
		if aerr, ok := err.(awserr.Error); ok {
			switch aerr.Code() {
			case ecr.ErrCodeRepositoryAlreadyExistsException:
				log.Println(ecr.ErrCodeRepositoryAlreadyExistsException, aerr.Error())
			default:
				log.Println(aerr.Error())
			}
		} else {
			log.Println(err.Error())
		}
	} else {
		log.Println("Success creating repo", ecrresult.Repository.RepositoryArn)
	}
	log.Println("The repo name is", accountid+".dkr.ecr.cn-north-1.amazonaws.com.cn/"+shortname)

	// Push image
	ecr_client := ecr.New(session.New(), aws.NewConfig().WithRegion("cn-north-1"))
	token_input := &ecr.GetAuthorizationTokenInput{}
	result, err := ecr_client.GetAuthorizationToken(token_input)
	if err != nil {
		fmt.Println(err.Error())
	}
	token := *result.AuthorizationData[0].AuthorizationToken
	decodedToken, err := base64.StdEncoding.DecodeString(token)
	if err != nil {
		fmt.Println("Error decoding token:", err)
		return
	}
	passwd := strings.Split(string(decodedToken), ":")[1]
	authConfig := types.AuthConfig{
		Username: "AWS",
		Password: passwd,
	}
	encodedJSON, _ := json.Marshal(authConfig)
	authStr := base64.URLEncoding.EncodeToString(encodedJSON)
	push_out, push_err := cli.ImagePush(context.TODO(), fmt.Sprintf("%s.dkr.ecr.cn-north-1.amazonaws.com.cn/%s:%s", accountid, shortname, tag), types.ImagePushOptions{RegistryAuth: authStr})
	if push_err != nil {
		log.Println("Unable to push image")
		return
	}
	io.Copy(os.Stdout, push_out)
	log.Println("Image pushed to ECR successfully")

	// Remove image
	_, removeerr := cli.ImageRemove(ctx, tagimage, types.ImageRemoveOptions{})
	if removeerr != nil {
		log.Println("Failed to remover image", err)
		return
	}
	log.Println("Clear temp image successfully")
	// return info
	log.Println("The image is successful pushed")
}

创建守护配置

sudo vim /etc/systemd/system/toimage.service
[Unit]
Description=my toimage daemon service
 
[Service]
Type=simple
Restart=always
RestartSec=5s
User=root
ExecStart=/home/ec2-user/toimage/toimage
 
[Install]
WantedBy=multi-user.target

启动服务

sudo systemctl daemon-reload
systemctl start toimage
systemctl enable toimage

测试拉取镜像

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值