Lambda GitHub 公共存储库定时备份存储

做这个程序来源于有次发现了 PHP 生成假数据 Faker 库,被作者关闭维护了,原因是库太大了、下载人数过多会影响全球变暖;或者是很多特殊的工具它突然就不见了,所以备份真的很重要。

开始 codeing , 创建一个目录

1
2
$ mkdir github_download
$ cd github_download

初始化 MOD

1
$ go mod init github_download

安装需要的依赖

1
2
$ go get github.com/aws/aws-lambda-go/lambda github.com/aws/aws-sdk-go/aws github.com/google/go-github/v33/github golang.org/x/oauth2
$ go mod download

index.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package main

import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/aws/aws-lambda-go/lambda"
"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"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/google/go-github/v33/github"
"golang.org/x/oauth2"
"io"
"io/ioutil"
netUrl "net/Url"
"net/http"
"os"
"path"
"strings"
"time"
)

// 临时存储目录
const TmpDir = "/tmp/"

func start() (err error) {
sess := session.Must(session.NewSession(&aws.Config{
Credentials: credentials.NewStaticCredentials(
os.Getenv("AWS_ACCESS_KEY_ID"),
os.Getenv("AWS_SECRET_ACCESS_KEY"),
os.Getenv("AWS_SESSION_TOKEN"),
),
Region: aws.String(os.Getenv("AWS_S3_REGION")),
}))

service := s3.New(sess)

ctx := context.Background()
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: os.Getenv("GITHUB_API_TOKEN")},
)
tc := oauth2.NewClient(ctx, ts)

client := github.NewClient(tc)

// 读取 config.json 配置文件
result, err := service.GetObject(&s3.GetObjectInput{
Bucket: aws.String(os.Getenv("AWS_S3_BUCKET")),
Key: aws.String("config.json"),
})

if err != nil {
return
}

defer result.Body.Close()

body, err := ioutil.ReadAll(result.Body)

if err != nil {
return
}

type UrlsListRequest struct {
Urls []string `json:"urls"`
}

var urls UrlsListRequest

// 格式化配置文件
err = json.Unmarshal(body, &urls)

if err != nil {
return
}

for _, url := range urls.Urls {
owner, repo, err := urlParse(url)

if err != nil {
fmt.Println(err)
continue
}

// 获取仓库的下载链接
downloadUrl, resp, err := client.Repositories.GetArchiveLink(ctx, owner, repo, "zipball", nil, true)

if err != nil {
fmt.Errorf("Repositories.GetArchiveLink returned error: %v", err)
continue
}

if resp.StatusCode != http.StatusFound {
fmt.Errorf("Repositories.GetArchiveLink returned status: %d, want %d", resp.StatusCode, http.StatusFound)
continue
}

// 拼接存储 KEY
filename := fmt.Sprintf("%s/%s/%s.zip", owner, repo, time.Now().Format("20060102-150405"))

err = downloadFile(downloadUrl.String(), TmpDir+filename)

if err != nil {
fmt.Println(err)
continue
}

err = uploadFile(filename, sess)

if err != nil {
fmt.Println(err)
continue
}
}

return
}

// 下载文件
func downloadFile(url string, filename string) (err error) {
fmt.Printf("开始下载文件: URL:%s,文件名:%s\n", url, filename)

dir := path.Dir(filename)

// 创建需要的目录
err = os.MkdirAll(dir, os.ModePerm)

if err != nil {
return
}

resp, err := http.Get(url)

if err != nil {
return
}

defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)

if err != nil {
return
}

file, err := os.Create(filename)

if err != nil {
return
}

defer file.Close()

_, err = io.Copy(file, bytes.NewReader(body))

if err != nil {
return
}

return nil
}

// 上传文件
func uploadFile(filename string, sess *session.Session) (err error) {
fmt.Printf("开始上传文件: 文件名:%s\n", filename)

file, err := os.Open(fmt.Sprintf("%s%s", TmpDir, filename))

if err != nil {
return
}

defer func() {
file.Close()

// 顺便把文件也删除了
os.Remove(fmt.Sprintf("%s%s", TmpDir, filename))
}()

uploader := s3manager.NewUploader(sess)

// 上传到 S3
_, err = uploader.Upload(&s3manager.UploadInput{
Bucket: aws.String(os.Getenv("AWS_S3_BUCKET")),
Key: aws.String(filename),
Body: file,
}, func(u *s3manager.Uploader) {
u.PartSize = 10 * 1024 * 1024 // 分片大小
})

return
}

// URL 解析出 owner 和 repo(用户名和仓库名)
func urlParse(url string) (owner string, repo string, err error) {
u, err := netUrl.Parse(url)

if err != nil {
return
}

if u.Host != "github.com" {
return
}

s := strings.Split(strings.TrimPrefix(u.Path, "/"), "/")

if len(s) < 2 {
return
}

return s[0], s[1], nil
}

func main() {
lambda.Start(start)
}

进行编译

1
$ env GOOS=linux GOARCH=amd64 go build -o .

编译后生成的文件我们需要在 Linux 系统里面添加上执行权限然后进行压缩打包,在文章最后我提供了已经弄好的包。

1
2
$ chmod 777 github_download
$ zip -r code.zip .

现在已经有了代码包,我们需要去 AWS 那边进行操作了,部署到 Lambda 前我们先创建一个 S3 存储桶。

有了存储桶,开始进行程序部署,创建我们需要的函数。

在代码源上传我们的 ZIP 代码包。

运行时设置这里需要将处理程序 hello 修改为我们的 github_download。

调整下函数的运行内存大小和超时时间,尽量大一些。

前往 GitHub 申请一个 API 令牌,不需要添加任何权限,主要是 GitHub API 是有请求限制的,有令牌就不会影响很大。

前往 S3 配置存储桶的权限。先到 https://awspolicygen.s3.amazonaws.com/policygen.html 进行配置生成。

注意:Amazon Resource Name (ARN) 后面需要加上 /* 表示这个存储桶的所有对象。

内容填写正确然后点击 Add Statement 接着再点击 Generate Policy

弹窗里面内容就是我们需要的配置。

把配置复制到 S3 的存储桶策略。

回到 Lambda 配置环境变量。

1
2
3
AWS_S3_BUCKET=      # 存储桶名称
AWS_S3_REGION= # 存储桶地域
GITHUB_API_TOKEN= # github api 令牌

离跑起来我们只差最后一步,本地创建一个配置文件来存储我们需要备份的地址。

config.json

1
2
3
4
5
6
7
{
"urls": [
"https://github.com/laravel/laravel",
"https://github.com/laravel/framework",
"https://github.com/overtrue/laravel-wechat"
]
}

把这个文件上传到存储桶的根目录,记得文件名为 config.json

好的,现在我们跑一下测试看看。

是的,我们已经跑起来了,S3 里面也可以看到是有文件的。

接下来配置下定时这块,测试所以我添加了个每分钟的,实际上正式用每天就差不多够了的。

过几分钟然后看看 S3 里面的效果。


源码下载

往上