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