forked from
whitequark.org/git-pages
fork of whitequark.org/git-pages with mods for tangled
1package git_pages
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "io"
8 "strings"
9)
10
11type UpdateOutcome int
12
13const (
14 UpdateError UpdateOutcome = iota
15 UpdateTimeout
16 UpdateCreated
17 UpdateReplaced
18 UpdateDeleted
19 UpdateNoChange
20)
21
22type UpdateResult struct {
23 outcome UpdateOutcome
24 manifest *Manifest
25 err error
26}
27
28func Update(ctx context.Context, webRoot string, manifest *Manifest) UpdateResult {
29 var oldManifest, newManifest *Manifest
30 var err error
31
32 outcome := UpdateError
33 oldManifest, _, _ = backend.GetManifest(ctx, webRoot, GetManifestOptions{})
34 if IsManifestEmpty(manifest) {
35 newManifest, err = manifest, backend.DeleteManifest(ctx, webRoot)
36 if err == nil {
37 if oldManifest == nil {
38 outcome = UpdateNoChange
39 } else {
40 outcome = UpdateDeleted
41 }
42 }
43 } else if err = PrepareManifest(ctx, manifest); err == nil {
44 newManifest, err = StoreManifest(ctx, webRoot, manifest)
45 if err == nil {
46 domain, _, _ := strings.Cut(webRoot, "/")
47 err = backend.CreateDomain(ctx, domain)
48 }
49 if err == nil {
50 if oldManifest == nil {
51 outcome = UpdateCreated
52 } else if CompareManifest(oldManifest, newManifest) {
53 outcome = UpdateNoChange
54 } else {
55 outcome = UpdateReplaced
56 }
57 }
58 }
59
60 if err == nil {
61 status := ""
62 switch outcome {
63 case UpdateCreated:
64 status = "created"
65 case UpdateReplaced:
66 status = "replaced"
67 case UpdateDeleted:
68 status = "deleted"
69 case UpdateNoChange:
70 status = "unchanged"
71 }
72 if newManifest.Commit != nil {
73 logc.Printf(ctx, "update %s ok: %s %s", webRoot, status, *newManifest.Commit)
74 } else {
75 logc.Printf(ctx, "update %s ok: %s", webRoot, status)
76 }
77 } else {
78 logc.Printf(ctx, "update %s err: %s", webRoot, err)
79 }
80
81 return UpdateResult{outcome, newManifest, err}
82}
83
84func UpdateFromRepository(
85 ctx context.Context,
86 webRoot string,
87 repoURL string,
88 branch string,
89) (result UpdateResult) {
90 span, ctx := ObserveFunction(ctx, "UpdateFromRepository", "repo.url", repoURL)
91 defer span.Finish()
92
93 logc.Printf(ctx, "update %s: %s %s\n", webRoot, repoURL, branch)
94
95 oldManifest, _, _ := backend.GetManifest(ctx, webRoot, GetManifestOptions{})
96 // Ignore errors; worst case we have to re-fetch all of the blobs.
97
98 manifest, err := FetchRepository(ctx, repoURL, branch, oldManifest)
99 if errors.Is(err, context.DeadlineExceeded) {
100 result = UpdateResult{UpdateTimeout, nil, fmt.Errorf("update timeout")}
101 } else if err != nil {
102 result = UpdateResult{UpdateError, nil, err}
103 } else {
104 result = Update(ctx, webRoot, manifest)
105 }
106
107 observeUpdateResult(result)
108 return result
109}
110
111var errArchiveFormat = errors.New("unsupported archive format")
112
113func UpdateFromArchive(
114 ctx context.Context,
115 webRoot string,
116 contentType string,
117 reader io.Reader,
118) (result UpdateResult) {
119 var manifest *Manifest
120 var err error
121
122 switch contentType {
123 case "application/x-tar":
124 logc.Printf(ctx, "update %s: (tar)", webRoot)
125 manifest, err = ExtractTar(reader) // yellow?
126 case "application/x-tar+gzip":
127 logc.Printf(ctx, "update %s: (tar.gz)", webRoot)
128 manifest, err = ExtractTarGzip(reader) // definitely yellow.
129 case "application/x-tar+zstd":
130 logc.Printf(ctx, "update %s: (tar.zst)", webRoot)
131 manifest, err = ExtractTarZstd(reader)
132 case "application/zip":
133 logc.Printf(ctx, "update %s: (zip)", webRoot)
134 manifest, err = ExtractZip(reader)
135 default:
136 err = errArchiveFormat
137 }
138
139 if err != nil {
140 logc.Printf(ctx, "update %s err: %s", webRoot, err)
141 result = UpdateResult{UpdateError, nil, err}
142 } else {
143 result = Update(ctx, webRoot, manifest)
144 }
145
146 observeUpdateResult(result)
147 return
148}
149
150func observeUpdateResult(result UpdateResult) {
151 if result.err != nil {
152 ObserveError(result.err)
153 }
154}