e1fe3bbdc0
This is an implementation of a quota engine, and the API routes to manage its settings. This does *not* contain any enforcement code: this is just the bedrock, the engine itself. The goal of the engine is to be flexible and future proof: to be nimble enough to build on it further, without having to rewrite large parts of it. It might feel a little more complicated than necessary, because the goal was to be able to support scenarios only very few Forgejo instances need, scenarios the vast majority of mostly smaller instances simply do not care about. The goal is to support both big and small, and for that, we need a solid, flexible foundation. There are thee big parts to the engine: counting quota use, setting limits, and evaluating whether the usage is within the limits. Sounds simple on paper, less so in practice! Quota counting ============== Quota is counted based on repo ownership, whenever possible, because repo owners are in ultimate control over the resources they use: they can delete repos, attachments, everything, even if they don't *own* those themselves. They can clean up, and will always have the permission and access required to do so. Would we count quota based on the owning user, that could lead to situations where a user is unable to free up space, because they uploaded a big attachment to a repo that has been taken private since. It's both more fair, and much safer to count quota against repo owners. This means that if user A uploads an attachment to an issue opened against organization O, that will count towards the quota of organization O, rather than user A. One's quota usage stats can be queried using the `/user/quota` API endpoint. To figure out what's eating into it, the `/user/repos?order_by=size`, `/user/quota/attachments`, `/user/quota/artifacts`, and `/user/quota/packages` endpoints should be consulted. There's also `/user/quota/check?subject=<...>` to check whether the signed-in user is within a particular quota limit. Quotas are counted based on sizes stored in the database. Setting quota limits ==================== There are different "subjects" one can limit usage for. At this time, only size-based limits are implemented, which are: - `size:all`: As the name would imply, the total size of everything Forgejo tracks. - `size:repos:all`: The total size of all repositories (not including LFS). - `size:repos:public`: The total size of all public repositories (not including LFS). - `size:repos:private`: The total size of all private repositories (not including LFS). - `sizeall`: The total size of all git data (including all repositories, and LFS). - `sizelfs`: The size of all git LFS data (either in private or public repos). - `size:assets:all`: The size of all assets tracked by Forgejo. - `size:assets:attachments:all`: The size of all kinds of attachments tracked by Forgejo. - `size:assets:attachments:issues`: Size of all attachments attached to issues, including issue comments. - `size:assets:attachments:releases`: Size of all attachments attached to releases. This does *not* include automatically generated archives. - `size:assets:artifacts`: Size of all Action artifacts. - `size:assets:packages:all`: Size of all Packages. - `size:wiki`: Wiki size Wiki size is currently not tracked, and the engine will always deem it within quota. These subjects are built into Rules, which set a limit on *all* subjects within a rule. Thus, we can create a rule that says: "1Gb limit on all release assets, all packages, and git LFS, combined". For a rule to stand, the total sum of all subjects must be below the rule's limit. Rules are in turn collected into groups. A group is just a name, and a list of rules. For a group to stand, all of its rules must stand. Thus, if we have a group with two rules, one that sets a combined 1Gb limit on release assets, all packages, and git LFS, and another rule that sets a 256Mb limit on packages, if the user has 512Mb of packages, the group will not stand, because the second rule deems it over quota. Similarly, if the user has only 128Mb of packages, but 900Mb of release assets, the group will not stand, because the combined size of packages and release assets is over the 1Gb limit of the first rule. Groups themselves are collected into Group Lists. A group list stands when *any* of the groups within stand. This allows an administrator to set conservative defaults, but then place select users into additional groups that increase some aspect of their limits. To top it off, it is possible to set the default quota groups a user belongs to in `app.ini`. If there's no explicit assignment, the engine will use the default groups. This makes it possible to avoid having to assign each and every user a list of quota groups, and only those need to be explicitly assigned who need a different set of groups than the defaults. If a user has any quota groups assigned to them, the default list will not be considered for them. The management APIs =================== This commit contains the engine itself, its unit tests, and the quota management APIs. It does not contain any enforcement. The APIs are documented in-code, and in the swagger docs, and the integration tests can serve as an example on how to use them. Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
164 lines
5.2 KiB
Go
164 lines
5.2 KiB
Go
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package structs
|
|
|
|
// QuotaInfo represents information about a user's quota
|
|
type QuotaInfo struct {
|
|
Used QuotaUsed `json:"used"`
|
|
Groups QuotaGroupList `json:"groups"`
|
|
}
|
|
|
|
// QuotaUsed represents the quota usage of a user
|
|
type QuotaUsed struct {
|
|
Size QuotaUsedSize `json:"size"`
|
|
}
|
|
|
|
// QuotaUsedSize represents the size-based quota usage of a user
|
|
type QuotaUsedSize struct {
|
|
Repos QuotaUsedSizeRepos `json:"repos"`
|
|
Git QuotaUsedSizeGit `json:"git"`
|
|
Assets QuotaUsedSizeAssets `json:"assets"`
|
|
}
|
|
|
|
// QuotaUsedSizeRepos represents the size-based repository quota usage of a user
|
|
type QuotaUsedSizeRepos struct {
|
|
// Storage size of the user's public repositories
|
|
Public int64 `json:"public"`
|
|
// Storage size of the user's private repositories
|
|
Private int64 `json:"private"`
|
|
}
|
|
|
|
// QuotaUsedSizeGit represents the size-based git (lfs) quota usage of a user
|
|
type QuotaUsedSizeGit struct {
|
|
// Storage size of the user's Git LFS objects
|
|
LFS int64 `json:"LFS"`
|
|
}
|
|
|
|
// QuotaUsedSizeAssets represents the size-based asset usage of a user
|
|
type QuotaUsedSizeAssets struct {
|
|
Attachments QuotaUsedSizeAssetsAttachments `json:"attachments"`
|
|
// Storage size used for the user's artifacts
|
|
Artifacts int64 `json:"artifacts"`
|
|
Packages QuotaUsedSizeAssetsPackages `json:"packages"`
|
|
}
|
|
|
|
// QuotaUsedSizeAssetsAttachments represents the size-based attachment quota usage of a user
|
|
type QuotaUsedSizeAssetsAttachments struct {
|
|
// Storage size used for the user's issue & comment attachments
|
|
Issues int64 `json:"issues"`
|
|
// Storage size used for the user's release attachments
|
|
Releases int64 `json:"releases"`
|
|
}
|
|
|
|
// QuotaUsedSizeAssetsPackages represents the size-based package quota usage of a user
|
|
type QuotaUsedSizeAssetsPackages struct {
|
|
// Storage suze used for the user's packages
|
|
All int64 `json:"all"`
|
|
}
|
|
|
|
// QuotaRuleInfo contains information about a quota rule
|
|
type QuotaRuleInfo struct {
|
|
// Name of the rule (only shown to admins)
|
|
Name string `json:"name,omitempty"`
|
|
// The limit set by the rule
|
|
Limit int64 `json:"limit"`
|
|
// Subjects the rule affects
|
|
Subjects []string `json:"subjects,omitempty"`
|
|
}
|
|
|
|
// QuotaGroupList represents a list of quota groups
|
|
type QuotaGroupList []QuotaGroup
|
|
|
|
// QuotaGroup represents a quota group
|
|
type QuotaGroup struct {
|
|
// Name of the group
|
|
Name string `json:"name,omitempty"`
|
|
// Rules associated with the group
|
|
Rules []QuotaRuleInfo `json:"rules"`
|
|
}
|
|
|
|
// CreateQutaGroupOptions represents the options for creating a quota group
|
|
type CreateQuotaGroupOptions struct {
|
|
// Name of the quota group to create
|
|
Name string `json:"name" binding:"Required"`
|
|
// Rules to add to the newly created group.
|
|
// If a rule does not exist, it will be created.
|
|
Rules []CreateQuotaRuleOptions `json:"rules"`
|
|
}
|
|
|
|
// CreateQuotaRuleOptions represents the options for creating a quota rule
|
|
type CreateQuotaRuleOptions struct {
|
|
// Name of the rule to create
|
|
Name string `json:"name" binding:"Required"`
|
|
// The limit set by the rule
|
|
Limit *int64 `json:"limit"`
|
|
// The subjects affected by the rule
|
|
Subjects []string `json:"subjects"`
|
|
}
|
|
|
|
// EditQuotaRuleOptions represents the options for editing a quota rule
|
|
type EditQuotaRuleOptions struct {
|
|
// The limit set by the rule
|
|
Limit *int64 `json:"limit"`
|
|
// The subjects affected by the rule
|
|
Subjects *[]string `json:"subjects"`
|
|
}
|
|
|
|
// SetUserQuotaGroupsOptions represents the quota groups of a user
|
|
type SetUserQuotaGroupsOptions struct {
|
|
// Quota groups the user shall have
|
|
// required: true
|
|
Groups *[]string `json:"groups"`
|
|
}
|
|
|
|
// QuotaUsedAttachmentList represents a list of attachment counting towards a user's quota
|
|
type QuotaUsedAttachmentList []*QuotaUsedAttachment
|
|
|
|
// QuotaUsedAttachment represents an attachment counting towards a user's quota
|
|
type QuotaUsedAttachment struct {
|
|
// Filename of the attachment
|
|
Name string `json:"name"`
|
|
// Size of the attachment (in bytes)
|
|
Size int64 `json:"size"`
|
|
// API URL for the attachment
|
|
APIURL string `json:"api_url"`
|
|
// Context for the attachment: URLs to the containing object
|
|
ContainedIn struct {
|
|
// API URL for the object that contains this attachment
|
|
APIURL string `json:"api_url"`
|
|
// HTML URL for the object that contains this attachment
|
|
HTMLURL string `json:"html_url"`
|
|
} `json:"contained_in"`
|
|
}
|
|
|
|
// QuotaUsedPackageList represents a list of packages counting towards a user's quota
|
|
type QuotaUsedPackageList []*QuotaUsedPackage
|
|
|
|
// QuotaUsedPackage represents a package counting towards a user's quota
|
|
type QuotaUsedPackage struct {
|
|
// Name of the package
|
|
Name string `json:"name"`
|
|
// Type of the package
|
|
Type string `json:"type"`
|
|
// Version of the package
|
|
Version string `json:"version"`
|
|
// Size of the package version
|
|
Size int64 `json:"size"`
|
|
// HTML URL to the package version
|
|
HTMLURL string `json:"html_url"`
|
|
}
|
|
|
|
// QuotaUsedArtifactList represents a list of artifacts counting towards a user's quota
|
|
type QuotaUsedArtifactList []*QuotaUsedArtifact
|
|
|
|
// QuotaUsedArtifact represents an artifact counting towards a user's quota
|
|
type QuotaUsedArtifact struct {
|
|
// Name of the artifact
|
|
Name string `json:"name"`
|
|
// Size of the artifact (compressed)
|
|
Size int64 `json:"size"`
|
|
// HTML URL to the action run containing the artifact
|
|
HTMLURL string `json:"html_url"`
|
|
}
|