-
Notifications
You must be signed in to change notification settings - Fork 0
/
latest.go
135 lines (114 loc) · 4.29 KB
/
latest.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
package main
import (
"os"
"encoding/json"
"fmt"
"path/filepath"
"time"
"strings"
"net/http"
)
type latestMetadata struct {
Version string `json:"version"`
}
const latestFileName = "..latest"
func readLatest(path string) (*latestMetadata, error) {
latest_path := filepath.Join(path, latestFileName)
latest_raw, err := os.ReadFile(latest_path)
if err != nil {
return nil, fmt.Errorf("failed to read '" + latest_path + "'; %w", err)
}
var output latestMetadata
err = json.Unmarshal(latest_raw, &output)
if err != nil {
return nil, fmt.Errorf("failed to parse JSON in '" + latest_path + "'; %w", err)
}
return &output, nil
}
func refreshLatest(asset_dir string) (*latestMetadata, error) {
entries, err := os.ReadDir(asset_dir)
if err != nil {
return nil, fmt.Errorf("failed to list versions in %q; %w", asset_dir, err)
}
found := false
var most_recent time.Time
var most_recent_name string
for _, entry := range entries {
if entry.IsDir() && !strings.HasPrefix(entry.Name(), ".") {
full_path := filepath.Join(asset_dir, entry.Name())
summ, err := readSummary(full_path)
if err != nil {
return nil, fmt.Errorf("failed to read summary from %q; %w", full_path, err)
}
if summ.IsProbational() {
continue
}
as_time, err := time.Parse(time.RFC3339, summ.UploadFinish)
if err != nil {
return nil, fmt.Errorf("could not parse 'upload_finish' from %q; %w", full_path, err)
}
if !found || most_recent.Before(as_time) {
most_recent = as_time
most_recent_name = entry.Name()
found = true
}
}
}
latest_path := filepath.Join(asset_dir, latestFileName)
if found {
output := latestMetadata { Version: most_recent_name }
err := dumpJson(latest_path, &output)
if err != nil {
return nil, fmt.Errorf("failed to update latest version in %q; %w", asset_dir, err)
}
return &output, nil
} else {
err := os.RemoveAll(latest_path)
if err != nil {
return nil, fmt.Errorf("failed to remove %q; %w", latest_path, err)
}
return nil, nil
}
}
func refreshLatestHandler(reqpath string, globals *globalConfiguration) (*latestMetadata, error) {
source_user, err := identifyUser(reqpath)
if err != nil {
return nil, fmt.Errorf("failed to find owner of %q; %w", reqpath, err)
}
if !isAuthorizedToAdmin(source_user, globals.Administrators) {
return nil, newHttpError(http.StatusForbidden, fmt.Errorf("user %q is not authorized to refresh the latest version (%q)", source_user, reqpath))
}
incoming := struct {
Project *string `json:"project"`
Asset *string `json:"asset"`
}{}
{
handle, err := os.ReadFile(reqpath)
if err != nil {
return nil, fmt.Errorf("failed to read %q; %w", reqpath, err)
}
err = json.Unmarshal(handle, &incoming)
if err != nil {
return nil, newHttpError(http.StatusBadRequest, fmt.Errorf("failed to parse JSON from %q; %w", reqpath, err))
}
err = isMissingOrBadName(incoming.Project)
if err != nil {
return nil, newHttpError(http.StatusBadRequest, fmt.Errorf("invalid 'project' property in %q; %w", reqpath, err))
}
err = isMissingOrBadName(incoming.Asset)
if err != nil {
return nil, newHttpError(http.StatusBadRequest, fmt.Errorf("invalid 'asset' property in %q; %w", reqpath, err))
}
}
// Technically we only need a lock on the asset directory, but all
// mutating operations will lock the project directory, so we respect that.
project_dir := filepath.Join(globals.Registry, *(incoming.Project))
err = globals.Locks.LockDirectory(project_dir, 10 * time.Second)
if err != nil {
return nil, fmt.Errorf("failed to acquire the lock on the project directory %q; %w", project_dir, err)
}
defer globals.Locks.Unlock(project_dir)
asset_dir := filepath.Join(project_dir, *(incoming.Asset))
output, err := refreshLatest(asset_dir)
return output, err
}