Skip to content
This repository was archived by the owner on Aug 6, 2021. It is now read-only.

Commit 709690f

Browse files
committed
import
1 parent dd1f7d5 commit 709690f

File tree

1,091 files changed

+347860
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,091 files changed

+347860
-0
lines changed

.github/workflows/ci.yaml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: PR
2+
on: push
3+
jobs:
4+
test:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- uses: actions/checkout@v2
8+
- uses: actions/setup-go@v2
9+
with:
10+
go-version: '1.15.0'
11+
- run: script/test
12+
- run: script/lint

.github/workflows/update.yaml

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Periodic Dependency Update
2+
on:
3+
schedule:
4+
- cron: '0 8 * * *'
5+
workflow_dispatch:
6+
pull_request:
7+
types: [reopened]
8+
9+
jobs:
10+
update:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v2
14+
with:
15+
token: ${{ secrets.MY_GITHUB_PAT }}
16+
- uses: actions/setup-go@v2
17+
with:
18+
go-version: '1.15.0'
19+
- uses: thepwagner/action-update-go@main
20+
with:
21+
log_level: debug
22+
token: ${{ secrets.MY_GITHUB_PAT }}

action.yml

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Update Docker URLs
2+
description: Update Docker URL dependencies
3+
author: 'thepwagner'
4+
inputs:
5+
branches:
6+
description: 'Branches to update'
7+
required: false
8+
token:
9+
description: >
10+
Personal access token (PAT) used to fetch the repository. The PAT is configured
11+
with the local git config, which enables your scripts to run authenticated git
12+
commands. The post-job step removes the PAT.
13+
We recommend using a service account with the least permissions necessary.
14+
Also when generating a new PAT, select the least scopes necessary.
15+
[Learn more about creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
16+
default: ${{ github.token }}
17+
required: true
18+
signing_key:
19+
default: "i deserve this"
20+
description: >
21+
Unique key to use for maintaining trusted metadata in PR body.
22+
required: false
23+
log_level:
24+
description: 'Control debug/info/warn/error output'
25+
required: false
26+
batches:
27+
description: >
28+
Configuration for grouping updates together, as a nested YAML of lists:
29+
e.g.
30+
internal: [github.com/thepwagner/]
31+
aws:
32+
- github.com/aws
33+
required: false
34+
runs:
35+
using: "composite"
36+
steps:
37+
- name: Verify Go SDK
38+
run: which go || echo "Go required, please use actions/setup-go before me"
39+
shell: bash
40+
- name: Compile action-update-dockerurl
41+
run: cd "${{github.action_path}}" && go build -o "${{github.action_path}}/action-update-dockerurl" .
42+
shell: bash
43+
- name: Run action-update-dockerurl
44+
run: ${{github.action_path}}/action-update-dockerurl
45+
shell: bash
46+
env:
47+
INPUT_BRANCHES: ${{ inputs.branches }}
48+
INPUT_BATCHES: ${{ inputs.batches }}
49+
INPUT_TOKEN: ${{ inputs.token }}
50+
INPUT_LOG_LEVEL: ${{ inputs.log_level }}

dockerurl/applyupdate.go

+311
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
package dockerurl
2+
3+
import (
4+
"context"
5+
"crypto/sha256"
6+
"crypto/sha512"
7+
"fmt"
8+
"hash"
9+
"io"
10+
"io/ioutil"
11+
"net/http"
12+
"os"
13+
"strings"
14+
15+
"github.com/google/go-github/v32/github"
16+
"github.com/moby/buildkit/frontend/dockerfile/parser"
17+
"github.com/sirupsen/logrus"
18+
"github.com/thepwagner/action-update-docker/docker"
19+
"github.com/thepwagner/action-update/updater"
20+
)
21+
22+
func (u *Updater) ApplyUpdate(ctx context.Context, update updater.Update) error {
23+
_, name := parseGitHubRelease(update.Path)
24+
nameUpper := strings.ToUpper(name)
25+
26+
// Potential keys the version/release hash might be stored under:
27+
versionKeys := []string{
28+
fmt.Sprintf("%s_VERSION", nameUpper),
29+
fmt.Sprintf("%s_RELEASE", nameUpper),
30+
}
31+
hashKeys := []string{
32+
fmt.Sprintf("%s_SHASUM", nameUpper),
33+
fmt.Sprintf("%s_SHA256SUM", nameUpper),
34+
fmt.Sprintf("%s_SHA512SUM", nameUpper),
35+
fmt.Sprintf("%s_CHECKSUM", nameUpper),
36+
}
37+
38+
return docker.WalkDockerfiles(u.root, func(path string, parsed *parser.Result) error {
39+
patterns := u.collectPatterns(ctx, parsed, versionKeys, hashKeys, update)
40+
logrus.WithFields(logrus.Fields{
41+
"path": path,
42+
"patterns": len(patterns),
43+
}).Debug("collected patterns")
44+
return updateDockerfile(path, patterns)
45+
})
46+
}
47+
48+
func (u *Updater) collectPatterns(ctx context.Context, parsed *parser.Result, versionKeys, hashKeys []string, update updater.Update) map[string]string {
49+
i := docker.NewInterpolation(parsed)
50+
patterns := map[string]string{}
51+
var versionHit bool
52+
for _, k := range versionKeys {
53+
prev, ok := i.Vars[k]
54+
if !ok {
55+
continue
56+
}
57+
switch prev {
58+
case update.Previous:
59+
logrus.WithFields(logrus.Fields{
60+
"key": k,
61+
"prefix": true,
62+
}).Debug("identified version key")
63+
patterns[fmt.Sprintf("%s=%s", k, prev)] = fmt.Sprintf("%s=%s", k, update.Next)
64+
versionHit = true
65+
case update.Previous[1:]:
66+
logrus.WithFields(logrus.Fields{
67+
"key": k,
68+
"prefix": false,
69+
}).Debug("identified version key")
70+
patterns[fmt.Sprintf("%s=%s", k, update.Previous[1:])] = fmt.Sprintf("%s=%s", k, update.Next[1:])
71+
versionHit = true
72+
}
73+
}
74+
if !versionHit {
75+
return patterns
76+
}
77+
78+
for _, k := range hashKeys {
79+
prev, ok := i.Vars[k]
80+
if !ok {
81+
continue
82+
}
83+
log := logrus.WithField("key", k)
84+
log.Debug("identified hash key")
85+
86+
newHash, err := u.updatedHash(ctx, update, prev)
87+
if err != nil {
88+
log.WithError(err).Warn("fetching updated hash")
89+
} else if newHash != "" {
90+
log.Debug("updated hash key")
91+
patterns[fmt.Sprintf("%s=%s", k, prev)] = fmt.Sprintf("%s=%s", k, newHash)
92+
}
93+
}
94+
return patterns
95+
}
96+
97+
func (u *Updater) updatedHash(ctx context.Context, update updater.Update, oldHash string) (string, error) {
98+
// Fetch the previous release:
99+
owner, repoName := parseGitHubRelease(update.Path)
100+
prevRelease, _, err := u.ghRepos.GetReleaseByTag(ctx, owner, repoName, update.Previous)
101+
if err != nil {
102+
return "", fmt.Errorf("fetching previous release: %w", err)
103+
}
104+
105+
// First pass, does the project release a SHASUMS etc file we can grab?
106+
for _, prevAsset := range prevRelease.Assets {
107+
log := logrus.WithField("name", prevAsset.GetName())
108+
oldAsset, err := u.isShasumAsset(ctx, prevAsset, oldHash)
109+
if err != nil {
110+
log.WithError(err).Warn("inspecting potential hash asset")
111+
continue
112+
} else if len(oldAsset) == 0 {
113+
log.Debug("old shasum asset not found")
114+
continue
115+
}
116+
log.Debug("identified shasum asset in previous release")
117+
118+
// The previous release contained a shasum file that contained the previous hash
119+
// Does the new release have the same file?
120+
newHash, err := u.updatedHashFromShasumAsset(ctx, prevAsset, oldAsset, oldHash, update)
121+
if err != nil {
122+
log.WithError(err).Warn("fetching updated hash asset")
123+
continue
124+
}
125+
if newHash != "" {
126+
log.Debug("fetched corresponding shasum asset from new release")
127+
return newHash, nil
128+
}
129+
}
130+
131+
// There are no shasum files - get downloading
132+
logrus.Debug("shasum file not found, searching files from previous release")
133+
for _, prevAsset := range prevRelease.Assets {
134+
log := logrus.WithField("name", prevAsset.GetName())
135+
h, err := u.isHashAsset(ctx, prevAsset, oldHash)
136+
if err != nil {
137+
log.WithError(err).Warn("checking hash of previous assets")
138+
continue
139+
} else if !h {
140+
continue
141+
}
142+
log.Debug("identified hashed asset in previous release")
143+
144+
// This asset from a previous release matched the previous hash
145+
// Does the new release have the same file?
146+
newHash, err := u.updatedHashFromAsset(ctx, prevAsset, update, oldHash)
147+
if err != nil {
148+
return "", err
149+
}
150+
if newHash != "" {
151+
log.Debug("fetched corresponding asset from new release")
152+
return newHash, nil
153+
}
154+
}
155+
156+
return "", nil
157+
}
158+
159+
// isShasumAsset returns true if the release asset is a SHASUMS file containing the previous hash
160+
func (u *Updater) isShasumAsset(ctx context.Context, asset *github.ReleaseAsset, oldHash string) ([]string, error) {
161+
if asset.GetSize() > 1024 {
162+
return nil, nil
163+
}
164+
165+
req, err := http.NewRequest("GET", asset.GetBrowserDownloadURL(), nil)
166+
if err != nil {
167+
return nil, err
168+
}
169+
req = req.WithContext(ctx)
170+
171+
res, err := u.http.Do(req)
172+
if err != nil {
173+
return nil, err
174+
}
175+
defer res.Body.Close()
176+
b, err := ioutil.ReadAll(res.Body)
177+
if err != nil {
178+
return nil, err
179+
}
180+
s := string(b)
181+
if !strings.Contains(s, oldHash) {
182+
return nil, nil
183+
}
184+
return strings.Split(s, "\n"), nil
185+
}
186+
187+
func (u *Updater) updatedHashFromShasumAsset(ctx context.Context, asset *github.ReleaseAsset, oldContents []string, oldHash string, update updater.Update) (string, error) {
188+
res, err := u.getUpdatedAsset(ctx, asset, update)
189+
if err != nil {
190+
return "", err
191+
}
192+
defer res.Body.Close()
193+
b, err := ioutil.ReadAll(res.Body)
194+
if err != nil {
195+
return "", err
196+
}
197+
s := string(b)
198+
199+
// If there's one line, extract the checksum and return it:
200+
if len(oldContents) == 1 {
201+
return strings.SplitN(s, " ", 2)[0], nil
202+
}
203+
204+
// If there's multiple lines, find the file corresponding the old hash:
205+
var hashedFile string
206+
for _, oldLine := range oldContents {
207+
split := strings.SplitN(oldLine, " ", 2)
208+
if split[0] == oldHash {
209+
hashedFile = split[1]
210+
}
211+
}
212+
if hashedFile == "" {
213+
return "", nil
214+
}
215+
216+
logrus.WithField("fn", hashedFile).Debug("identified hashed file in shasum asset")
217+
for _, newLine := range strings.Split(s, "\n") {
218+
split := strings.SplitN(newLine, " ", 2)
219+
if len(split) == 1 {
220+
continue
221+
}
222+
if split[1] == hashedFile {
223+
return split[0], nil
224+
}
225+
}
226+
227+
return "", nil
228+
}
229+
230+
func (u *Updater) getUpdatedAsset(ctx context.Context, asset *github.ReleaseAsset, update updater.Update) (*http.Response, error) {
231+
newURL := asset.GetBrowserDownloadURL()
232+
newURL = strings.ReplaceAll(newURL, update.Previous, update.Next)
233+
newURL = strings.ReplaceAll(newURL, update.Previous[1:], update.Next[1:])
234+
req, err := http.NewRequest("GET", newURL, nil)
235+
if err != nil {
236+
return nil, err
237+
}
238+
req = req.WithContext(ctx)
239+
return u.http.Do(req)
240+
}
241+
242+
func (u *Updater) isHashAsset(ctx context.Context, asset *github.ReleaseAsset, oldHash string) (bool, error) {
243+
h, ok := hasher(oldHash)
244+
if !ok {
245+
return false, nil
246+
}
247+
248+
req, err := http.NewRequest("GET", asset.GetBrowserDownloadURL(), nil)
249+
if err != nil {
250+
return false, err
251+
}
252+
req = req.WithContext(ctx)
253+
254+
res, err := u.http.Do(req)
255+
if err != nil {
256+
return false, err
257+
}
258+
defer res.Body.Close()
259+
if _, err := io.Copy(h, res.Body); err != nil {
260+
return false, err
261+
}
262+
sum := h.Sum(nil)
263+
return fmt.Sprintf("%x", sum) == oldHash, nil
264+
}
265+
266+
func hasher(oldHash string) (hash.Hash, bool) {
267+
switch len(oldHash) {
268+
case 64:
269+
return sha256.New(), true
270+
case 128:
271+
return sha512.New(), true
272+
default:
273+
return nil, false
274+
}
275+
}
276+
277+
func (u *Updater) updatedHashFromAsset(ctx context.Context, asset *github.ReleaseAsset, update updater.Update, oldHash string) (string, error) {
278+
res, err := u.getUpdatedAsset(ctx, asset, update)
279+
if err != nil {
280+
return "", err
281+
}
282+
defer res.Body.Close()
283+
h, _ := hasher(oldHash)
284+
if _, err := io.Copy(h, res.Body); err != nil {
285+
return "", err
286+
}
287+
sum := h.Sum(nil)
288+
return fmt.Sprintf("%x", sum), nil
289+
}
290+
291+
func updateDockerfile(path string, patterns map[string]string) error {
292+
// Buffer contents as a string
293+
b, err := ioutil.ReadFile(path)
294+
if err != nil {
295+
return fmt.Errorf("reading file: %w", err)
296+
}
297+
s := string(b)
298+
299+
for old, replace := range patterns {
300+
s = strings.ReplaceAll(s, old, replace)
301+
}
302+
303+
stat, err := os.Stat(path)
304+
if err != nil {
305+
return fmt.Errorf("stating file: %w", err)
306+
}
307+
if err := ioutil.WriteFile(path, []byte(s), stat.Mode()); err != nil {
308+
return fmt.Errorf("writing updated file: %w", err)
309+
}
310+
return nil
311+
}

0 commit comments

Comments
 (0)