detached 83e495f

ci_artifact.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package domain
4
5import (
6	"fmt"
7	"strings"
8	"time"
9	"unicode"
10	"unicode/utf8"
11)
12
13// Limits and defaults of the CI artifacts (artifacts v4, CI-4 D3).
14const (
15	// CIArtifactMaxNameLen is the longest artifact name, in characters.
16	CIArtifactMaxNameLen = 256
17	// CIArtifactMaxMediaTypeLen is the longest media type of an artifact.
18	CIArtifactMaxMediaTypeLen = 255
19	// CIArtifactMaxBlockIDLen is the longest block id (Azure's, decoded).
20	CIArtifactMaxBlockIDLen = 64
21	// CIArtifactMaxBlocks bounds the blocks of a commit and the blocks staged
22	// for one artifact.
23	CIArtifactMaxBlocks = 50_000
24	// CIArtifactMaxPendingPerJob bounds the unfinished uploads of a job, and
25	// CIArtifactMaxPerRun the artifacts of a run, unfinished ones included
26	// (the toolkit uploads one artifact at a time).
27	CIArtifactMaxPendingPerJob = 32
28	CIArtifactMaxPerRun        = 1000
29	// CIArtifactChunk is the largest piece of data of one message, both ways.
30	CIArtifactChunk = 1 << 20
31	// CIArtifactMinRetention is the shortest retention of an artifact.
32	CIArtifactMinRetention = 24 * time.Hour
33	// CIArtifactPendingMax is how long an upload may stay unfinished before the
34	// sweep deletes it, whatever its job does.
35	CIArtifactPendingMax = 24 * time.Hour
36	// DefaultCIArtifactQuota is the space the artifacts of one repository may
37	// use, uploads in progress included.
38	DefaultCIArtifactQuota = 10 << 30
39	// DefaultCIArtifactMaxSize is the largest artifact.
40	DefaultCIArtifactMaxSize = 5 << 30
41	// CIArtifactZip is the media type of an archive.
42	CIArtifactZip = "application/zip"
43)
44
45// CIArtifactState is where an artifact is in its upload.
46type CIArtifactState string
47
48const (
49	// CIArtifactPending is created, its blocks being uploaded.
50	CIArtifactPending CIArtifactState = "pending"
51	// CIArtifactCommitted has its content; not visible yet.
52	CIArtifactCommitted CIArtifactState = "committed"
53	// CIArtifactFinal is finalized: visible to the run and its owners.
54	CIArtifactFinal CIArtifactState = "final"
55)
56
57// CIArtifact is a file that a job of a run uploaded with
58// actions/upload-artifact. It belongs to the run: every job of the run, in any
59// attempt, sees the finalized ones.
60type CIArtifact struct {
61	// ID is global, monotonic and never reused.
62	ID    int64
63	Repo  RepoName
64	RunID int64
65	// Attempt is the attempt of the run of the job that uploads it.
66	Attempt int32
67	JobID   string
68	// Assignment is the hash of the job token of the assignment that uploads
69	// it: a pending artifact belongs to that assignment only.
70	Assignment TokenHash
71	Name       string
72	MediaType  string
73	Size       int64
74	// Digest is "sha256:<hex>" of the content, known from the commit.
75	Digest    string
76	State     CIArtifactState
77	CreatedAt time.Time
78	ExpiresAt time.Time
79	// Charged is what the artifact counts against the quota of its
80	// repository: the blocks staged while pending, then the content.
81	Charged int64
82	// ListDigest identifies the block list of the commit.
83	ListDigest []byte
84	// Version is the store version of the record; 0 for a new one.
85	Version uint64
86}
87
88// Final reports whether the artifact is finalized.
89func (a CIArtifact) Final() bool { return a.State == CIArtifactFinal }
90
91// Visible reports whether the artifact is finalized and not expired at now.
92func (a CIArtifact) Visible(now time.Time) bool { return a.Final() && now.Before(a.ExpiresAt) }
93
94// UploadedBy reports whether the job, at its run attempt, uploads or uploaded
95// the artifact. It says nothing of the assignment.
96func (a CIArtifact) UploadedBy(j CIJob) bool {
97	return a.RunID == j.RunID && a.Attempt == j.RunAttempt && a.JobID == j.ID
98}
99
100// PendingOf reports whether the artifact is an unfinished upload of the
101// current assignment of the job.
102func (a CIArtifact) PendingOf(j CIJob) bool {
103	return !a.Final() && a.UploadedBy(j) && a.Assignment == j.TokenHash && j.HasToken()
104}
105
106// ParseArtifactName checks an artifact name: 1 to 256 characters of valid
107// UTF-8, without control characters, '"', ':', '<', '>', '|', '*', '?', '\'
108// or '/' (the characters @actions/artifact refuses). A name is never used as a
109// path: these rules are the toolkit's.
110func ParseArtifactName(name string) error {
111	if name == "" || !utf8.ValidString(name) || utf8.RuneCountInString(name) > CIArtifactMaxNameLen {
112		return fmt.Errorf("%w: an artifact name has 1 to %d characters of valid UTF-8", ErrInvalid, CIArtifactMaxNameLen)
113	}
114	for _, r := range name {
115		if unicode.IsControl(r) || strings.ContainsRune(`":<>|*?\/`, r) {
116			return fmt.Errorf("%w: an artifact name has no control character and none of \" : < > | * ? \\ /", ErrInvalid)
117		}
118	}
119	return nil
120}
121
122// ParseArtifactMediaType checks the media type of an artifact uploaded as is
123// and returns it, CIArtifactZip for an empty one: at most 255 printable ASCII
124// characters.
125func ParseArtifactMediaType(mt string) (string, error) {
126	if mt == "" {
127		return CIArtifactZip, nil
128	}
129	if len(mt) > CIArtifactMaxMediaTypeLen {
130		return "", fmt.Errorf("%w: a media type has at most %d characters", ErrInvalid, CIArtifactMaxMediaTypeLen)
131	}
132	for i := 0; i < len(mt); i++ {
133		if mt[i] < 0x20 || mt[i] > 0x7e {
134			return "", fmt.Errorf("%w: a media type has printable ASCII characters only", ErrInvalid)
135		}
136	}
137	return mt, nil
138}
139
140// ParseArtifactDigest checks a digest given by an uploader: "sha256:" and 64
141// hexadecimal characters, returned in lower case. Empty is no digest.
142func ParseArtifactDigest(d string) (string, error) {
143	if d == "" {
144		return "", nil
145	}
146	hex, ok := strings.CutPrefix(strings.ToLower(d), "sha256:")
147	if !ok || len(hex) != 64 || strings.Trim(hex, "0123456789abcdef") != "" {
148		return "", fmt.Errorf("%w: a digest is \"sha256:\" and 64 hexadecimal characters", ErrInvalid)
149	}
150	return "sha256:" + hex, nil
151}
152
153// CheckArtifactBlockID checks a block id: 1 to 64 bytes.
154func CheckArtifactBlockID(id []byte) error {
155	if len(id) == 0 || len(id) > CIArtifactMaxBlockIDLen {
156		return fmt.Errorf("%w: a block id has 1 to %d bytes", ErrInvalid, CIArtifactMaxBlockIDLen)
157	}
158	return nil
159}
160
161// ArtifactExpiry is the expiry an artifact gets: the one asked for (zero:
162// none) kept between one day from now and the longest retention, now plus the
163// retention without one. A retention below one day counts as one day.
164func ArtifactExpiry(asked, now time.Time, retention time.Duration) time.Time {
165	retention = max(retention, CIArtifactMinRetention)
166	switch {
167	case asked.IsZero():
168		return now.Add(retention)
169	case asked.Before(now.Add(CIArtifactMinRetention)):
170		return now.Add(CIArtifactMinRetention)
171	case asked.After(now.Add(retention)):
172		return now.Add(retention)
173	}
174	return asked
175}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 45

↑↓ moveenter openesc close