S3命令行工具Docker容器運(yùn)行過程
源代碼
package main
import (
"bytes"
"context"
"flag"
"fmt"
"io"
"log"
"os"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)
// S3Client 封裝 AWS S3 客戶端,提供面向?qū)ο蠼涌?
type S3Client struct {
ctx context.Context
svc *s3.S3
logger *log.Logger
}
// NewS3Client 創(chuàng)建一個(gè)新的 S3Client 實(shí)例
func NewS3Client(ctx context.Context, endpoint, accessKey, secretKey, region string, disableSSL, forcePathStyle bool, logger *log.Logger) (*S3Client, error) {
conf := &aws.Config{
Endpoint: aws.String(endpoint),
S3ForcePathStyle: aws.Bool(forcePathStyle),
DisableSSL: aws.Bool(disableSSL),
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
Region: aws.String(region),
}
sess, err := session.NewSessionWithOptions(session.Options{Config: *conf})
if err != nil {
return nil, fmt.Errorf("創(chuàng)建 AWS session 失敗: %v", err)
}
return &S3Client{
ctx: ctx,
svc: s3.New(sess),
logger: logger,
}, nil
}
// CreateBucket 創(chuàng)建存儲(chǔ)桶
func (c *S3Client) CreateBucket(bucketName string) error {
input := &s3.CreateBucketInput{
Bucket: aws.String(bucketName),
}
_, err := c.svc.CreateBucket(input)
if err != nil {
return fmt.Errorf("創(chuàng)建存儲(chǔ)桶失敗: %v", err)
}
c.logger.Printf("存儲(chǔ)桶 %s 創(chuàng)建成功", bucketName)
return nil
}
// ListBuckets 列出所有存儲(chǔ)桶
func (c *S3Client) ListBuckets() error {
resp, err := c.svc.ListBuckets(&s3.ListBucketsInput{})
if err != nil {
return fmt.Errorf("列出存儲(chǔ)桶失敗: %v", err)
}
if len(resp.Buckets) == 0 {
fmt.Println("沒有找到任何存儲(chǔ)桶")
return nil
}
fmt.Println("存儲(chǔ)桶列表:")
for _, b := range resp.Buckets {
fmt.Printf(" - %s (創(chuàng)建時(shí)間: %s)\n", aws.StringValue(b.Name), aws.TimeValue(b.CreationDate).Format(time.RFC3339))
}
return nil
}
// ListObjects 列出指定存儲(chǔ)桶中的對(duì)象
func (c *S3Client) ListObjects(bucketName string) error {
input := &s3.ListObjectsInput{
Bucket: aws.String(bucketName),
}
resp, err := c.svc.ListObjects(input)
if err != nil {
return fmt.Errorf("列出對(duì)象失敗: %v", err)
}
if len(resp.Contents) == 0 {
fmt.Printf("存儲(chǔ)桶 %s 中沒有對(duì)象\n", bucketName)
return nil
}
fmt.Printf("存儲(chǔ)桶 %s 中的對(duì)象:\n", bucketName)
for _, obj := range resp.Contents {
fmt.Printf(" - %s (大小: %d bytes, 修改時(shí)間: %s)\n",
aws.StringValue(obj.Key), aws.Int64Value(obj.Size), aws.TimeValue(obj.LastModified).Format(time.RFC3339))
}
return nil
}
// PutObject 上傳對(duì)象到存儲(chǔ)桶
func (c *S3Client) PutObject(bucketName, objectName, filePath string) error {
data, err := os.ReadFile(filePath)
if err != nil {
return fmt.Errorf("讀取文件失敗: %v", err)
}
ctx, cancel := context.WithTimeout(c.ctx, 30*time.Second)
defer cancel()
_, err = c.svc.PutObjectWithContext(ctx, &s3.PutObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectName),
Body: bytes.NewReader(data),
ACL: aws.String("public-read"),
})
if err != nil {
return fmt.Errorf("上傳對(duì)象失敗: %v", err)
}
c.logger.Printf("對(duì)象 %s 已上傳到 %s", objectName, bucketName)
return nil
}
// GetObject 從存儲(chǔ)桶下載對(duì)象
func (c *S3Client) GetObject(bucketName, objectName, outputFile string) error {
ctx, cancel := context.WithTimeout(c.ctx, 30*time.Second)
defer cancel()
out, err := c.svc.GetObjectWithContext(ctx, &s3.GetObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectName),
})
if err != nil {
return fmt.Errorf("下載對(duì)象失敗: %v", err)
}
defer out.Body.Close()
data, err := io.ReadAll(out.Body)
if err != nil {
return fmt.Errorf("讀取對(duì)象內(nèi)容失敗: %v", err)
}
if outputFile == "" {
fmt.Print(string(data))
} else {
if err := os.WriteFile(outputFile, data, 0644); err != nil {
return fmt.Errorf("寫入文件失敗: %v", err)
}
c.logger.Printf("對(duì)象 %s 已保存到 %s", objectName, outputFile)
}
return nil
}
// GetObjectAcl 獲取對(duì)象的 ACL 并打印
func (c *S3Client) GetObjectAcl(bucketName, objectName string) error {
out, err := c.svc.GetObjectAcl(&s3.GetObjectAclInput{
Bucket: aws.String(bucketName),
Key: aws.String(objectName),
})
if err != nil {
return fmt.Errorf("獲取對(duì)象 ACL 失敗: %v", err)
}
fmt.Printf("對(duì)象 %s 的 ACL:\n", objectName)
fmt.Printf(" 所有者: %s\n", aws.StringValue(out.Owner.DisplayName))
for _, grant := range out.Grants {
grantee := grant.Grantee
permission := aws.StringValue(grant.Permission)
if grantee.DisplayName != nil {
fmt.Printf(" - 授權(quán)給 %s: %s\n", aws.StringValue(grantee.DisplayName), permission)
} else if grantee.URI != nil {
fmt.Printf(" - 授權(quán)給 %s: %s\n", aws.StringValue(grantee.URI), permission)
} else if grantee.ID != nil {
fmt.Printf(" - 授權(quán)給 %s: %s\n", aws.StringValue(grantee.ID), permission)
}
}
return nil
}
// 命令行參數(shù)結(jié)構(gòu)
type config struct {
endpoint string
accessKey string
secretKey string
region string
disableSSL bool
forcePathStyle bool
}
func parseFlags() *config {
cfg := &config{}
flag.StringVar(&cfg.endpoint, "endpoint", getEnv("S3_ENDPOINT", ""), "S3 endpoint URL")
flag.StringVar(&cfg.accessKey, "access-key", getEnv("AWS_ACCESS_KEY_ID", ""), "AWS access key ID")
flag.StringVar(&cfg.secretKey, "secret-key", getEnv("AWS_SECRET_ACCESS_KEY", ""), "AWS secret access key")
flag.StringVar(&cfg.region, "region", getEnv("AWS_REGION", "cn"), "AWS region")
flag.BoolVar(&cfg.disableSSL, "disable-ssl", getEnvBool("S3_DISABLE_SSL", true), "disable SSL")
flag.BoolVar(&cfg.forcePathStyle, "force-path-style", getEnvBool("S3_FORCE_PATH_STYLE", false), "force path style")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "用法: %s [全局選項(xiàng)] <子命令> [子命令參數(shù)]\n", os.Args[0])
fmt.Fprintf(os.Stderr, "\n全局選項(xiàng):\n")
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\n子命令:\n")
fmt.Fprintf(os.Stderr, " create-bucket <bucket> 創(chuàng)建存儲(chǔ)桶\n")
fmt.Fprintf(os.Stderr, " list-buckets 列出所有存儲(chǔ)桶\n")
fmt.Fprintf(os.Stderr, " list-objects <bucket> 列出存儲(chǔ)桶中的對(duì)象\n")
fmt.Fprintf(os.Stderr, " put-object --bucket <bucket> --key <key> --file <path> 上傳文件\n")
fmt.Fprintf(os.Stderr, " get-object --bucket <bucket> --key <key> [--output <file>] 下載對(duì)象(不指定輸出則打印到 stdout)\n")
fmt.Fprintf(os.Stderr, " get-object-acl --bucket <bucket> --key <key> 獲取對(duì)象 ACL\n")
fmt.Fprintf(os.Stderr, "\n環(huán)境變量:\n")
fmt.Fprintf(os.Stderr, " S3_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, S3_DISABLE_SSL, S3_FORCE_PATH_STYLE\n")
}
flag.Parse()
return cfg
}
func getEnv(key, defaultValue string) string {
if val := os.Getenv(key); val != "" {
return val
}
return defaultValue
}
func getEnvBool(key string, defaultValue bool) bool {
if val := os.Getenv(key); val != "" {
return val == "true" || val == "1"
}
return defaultValue
}
func main() {
// S3_ENDPOINT="https://..." AWS_ACCESS_KEY_ID="..." AWS_SECRET_ACCESS_KEY="..." go run main.go put-object --bucket my-bucket --key myfile.jpg --file ./photo.jpg
cfg := parseFlags()
// 驗(yàn)證必需參數(shù)
if cfg.endpoint == "" {
log.Fatal("錯(cuò)誤: 必須指定 endpoint (通過 -endpoint 或環(huán)境變量 S3_ENDPOINT)")
}
if cfg.accessKey == "" {
log.Fatal("錯(cuò)誤: 必須指定 access-key (通過 -access-key 或環(huán)境變量 AWS_ACCESS_KEY_ID)")
}
if cfg.secretKey == "" {
log.Fatal("錯(cuò)誤: 必須指定 secret-key (通過 -secret-key 或環(huán)境變量 AWS_SECRET_ACCESS_KEY)")
}
// 創(chuàng)建日志器和上下文
logger := log.New(os.Stderr, "[S3] ", log.LstdFlags)
ctx := context.Background()
// 實(shí)例化客戶端
client, err := NewS3Client(ctx, cfg.endpoint, cfg.accessKey, cfg.secretKey, cfg.region, cfg.disableSSL, cfg.forcePathStyle, logger)
if err != nil {
log.Fatalf("初始化 S3 客戶端失敗: %v", err)
}
// 獲取子命令
args := flag.Args()
if len(args) == 0 {
flag.Usage()
os.Exit(1)
}
cmd := args[0]
subArgs := args[1:]
switch cmd {
case "create-bucket":
if len(subArgs) < 1 {
log.Fatal("用法: create-bucket <bucket>")
}
if err := client.CreateBucket(subArgs[0]); err != nil {
log.Fatal(err)
}
case "list-buckets":
if err := client.ListBuckets(); err != nil {
log.Fatal(err)
}
case "list-objects":
if len(subArgs) < 1 {
log.Fatal("用法: list-objects <bucket>")
}
if err := client.ListObjects(subArgs[0]); err != nil {
log.Fatal(err)
}
case "put-object":
fs := flag.NewFlagSet("put-object", flag.ExitOnError)
bucket := fs.String("bucket", "", "存儲(chǔ)桶名稱")
key := fs.String("key", "", "對(duì)象鍵名")
file := fs.String("file", "", "要上傳的文件路徑")
fs.Parse(subArgs)
if *bucket == "" || *key == "" || *file == "" {
log.Fatal("put-object 需要 --bucket, --key, --file 參數(shù)")
}
if err := client.PutObject(*bucket, *key, *file); err != nil {
log.Fatal(err)
}
case "get-object":
fs := flag.NewFlagSet("get-object", flag.ExitOnError)
bucket := fs.String("bucket", "", "存儲(chǔ)桶名稱")
key := fs.String("key", "", "對(duì)象鍵名")
output := fs.String("output", "", "輸出文件路徑(可選)")
fs.Parse(subArgs)
if *bucket == "" || *key == "" {
log.Fatal("get-object 需要 --bucket 和 --key 參數(shù)")
}
if err := client.GetObject(*bucket, *key, *output); err != nil {
log.Fatal(err)
}
case "get-object-acl":
fs := flag.NewFlagSet("get-object-acl", flag.ExitOnError)
bucket := fs.String("bucket", "", "存儲(chǔ)桶名稱")
key := fs.String("key", "", "對(duì)象鍵名")
fs.Parse(subArgs)
if *bucket == "" || *key == "" {
log.Fatal("get-object-acl 需要 --bucket 和 --key 參數(shù)")
}
if err := client.GetObjectAcl(*bucket, *key); err != nil {
log.Fatal(err)
}
default:
log.Fatalf("未知子命令: %s\n", cmd)
flag.Usage()
os.Exit(1)
}
}
Dockerfile
# 使用多階段構(gòu)建 (Multi-stage build) 以減小最終鏡像體積
# 第一階段:構(gòu)建階段
FROM golang:1.20-alpine AS builder
# 設(shè)置 Go 環(huán)境變量:?jiǎn)⒂?Go Modules 并使用國內(nèi)代理
ENV GO111MODULE=on \
GOPROXY=https://goproxy.cn,direct
# (可選)更換 Alpine 軟件源為國內(nèi)鏡像,加速后續(xù)可能出現(xiàn)的 apk 操作
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
# 設(shè)置工作目錄
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
# 復(fù)制源代碼
COPY . .
# 構(gòu)建可執(zhí)行文件,關(guān)閉 CGO 以生成靜態(tài)鏈接的二進(jìn)制文件
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o s3-tool main.go
# 第二階段:運(yùn)行階段
FROM alpine:latest
# 更換 Alpine 軟件源為國內(nèi)鏡像(中科大源)
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
# 安裝 CA 證書,以便在 HTTPS 請(qǐng)求中驗(yàn)證 SSL 證書
RUN apk --no-cache add ca-certificates
# 設(shè)置工作目錄
WORKDIR /root/
# 從構(gòu)建階段復(fù)制可執(zhí)行文件
COPY --from=builder /app/s3-tool .
# 設(shè)置容器啟動(dòng)時(shí)的默認(rèn)命令,顯示幫助信息
ENTRYPOINT ["./s3-tool"]
容器方式運(yùn)行
構(gòu)建鏡像
docker build -t s3-tool:latest .
方法一:使用環(huán)境變量文件(推薦)
創(chuàng)建一個(gè) s3.env 文件:
S3_ENDPOINT=https://your-s3-endpoint.com AWS_ACCESS_KEY_ID=your-access-key AWS_SECRET_ACCESS_KEY=your-secret-key AWS_REGION=us-east-1 S3_DISABLE_SSL=false S3_FORCE_PATH_STYLE=false
- 列出所有存儲(chǔ)桶
docker run --rm --env-file s3.env s3-tool:latest list-buckets
- 創(chuàng)建新存儲(chǔ)桶
docker run --rm --env-file s3.env s3-tool:latest create-bucket my-new-bucket
- 上傳文件(需要掛載宿主機(jī)文件)
docker run --rm \ --env-file s3.env \ -v /path/to/your/local/file:/tmp/upload-file \ s3-tool:latest put-object --bucket my-bucket --key remote-key --file /tmp/upload-file
說明:使用 -v 將宿主機(jī)文件掛載到容器內(nèi)可訪問的路徑。
- 下載對(duì)象到宿主機(jī)
docker run --rm \ --env-file s3.env \ -v /path/to/save/on/host:/tmp/download \ s3-tool:latest get-object --bucket my-bucket --key remote-key --output /tmp/download/local-file.txt
說明:下載的文件會(huì)出現(xiàn)在宿主機(jī)的 /path/to/save/on/host/local-file.txt。
方法二:逐個(gè)傳遞環(huán)境變量
- 列出所有存儲(chǔ)桶
docker run --rm \ -e S3_ENDPOINT="https://your-s3-endpoint.com" \ -e AWS_ACCESS_KEY_ID="your-access-key" \ -e AWS_SECRET_ACCESS_KEY="your-secret-key" \ -e AWS_REGION="us-east-1" \ s3-tool:latest list-buckets
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Docker部署Minio集群的實(shí)現(xiàn)步驟
本文介紹 Docker Compose 部署 Minio 集群的方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2026-05-05
Docker 部署Go的兩種基礎(chǔ)鏡像的實(shí)現(xiàn)
這篇文章主要介紹了Docker 部署Go的兩種基礎(chǔ)鏡像的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
解決docker鏡像(centos系統(tǒng))中無sudo命令問題
這篇文章主要介紹了解決docker鏡像(centos系統(tǒng))中無sudo命令問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,2023-11-11
使用Docker搭建MySQL數(shù)據(jù)庫服務(wù)的方法詳解
在現(xiàn)代應(yīng)用程序開發(fā)中,使用數(shù)據(jù)庫是必不可少的,文將介紹如何使用Docker搭建MySQL數(shù)據(jù)庫服務(wù),讓你在本地環(huán)境中快速部署一個(gè)MySQL實(shí)例,感興趣的可以了解下2024-03-03
docker報(bào)錯(cuò)Container is not running問題及解決
這篇文章主要介紹了docker報(bào)錯(cuò)Container is not running問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05
Docker學(xué)習(xí)筆記之Docker部署Java web系統(tǒng)
這篇文章主要介紹了Docker學(xué)習(xí)筆記之Docker部署Java web系統(tǒng),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02

