commands/release/releaseutils/upload/upload.go (131 lines of code) (raw):
package upload
import (
"fmt"
"io"
gitlab "gitlab.com/gitlab-org/api/client-go"
"gitlab.com/gitlab-org/cli/pkg/iostreams"
)
// ConflictDirectAssetPathError is returned when both direct_asset_path and the deprecated filepath as specified for an asset link.
type ConflictDirectAssetPathError struct {
assetLinkName *string
}
func (e *ConflictDirectAssetPathError) Error() string {
var name string
if e.assetLinkName != nil {
name = *e.assetLinkName
} else {
name = "(without a name)"
}
return fmt.Sprintf("asset link %s contains both `direct_asset_path` and `filepath` (deprecated) fields. Remove the deprecated `filepath` field.", name)
}
type ReleaseAsset struct {
Name *string `json:"name,omitempty"`
URL *string `json:"url,omitempty"`
// Deprecated FilePath use DirectAssetPath instead.
FilePath *string `json:"filepath,omitempty"`
DirectAssetPath *string `json:"direct_asset_path,omitempty"`
LinkType *gitlab.LinkTypeValue `json:"link_type,omitempty"`
}
type ReleaseFile struct {
Open func() (io.ReadCloser, error)
Name string
Label string
Path string
Type *gitlab.LinkTypeValue
}
func CreateLink(c *gitlab.Client, projectID, tagName string, asset *ReleaseAsset) (*gitlab.ReleaseLink, bool /* aliased deprecated filepath */, error) {
aliased, err := aliasFilePathToDirectAssetPath(asset)
if err != nil {
return nil, false, err
}
releaseLink, _, err := c.ReleaseLinks.CreateReleaseLink(projectID, tagName, &gitlab.CreateReleaseLinkOptions{
Name: asset.Name,
URL: asset.URL,
DirectAssetPath: asset.DirectAssetPath,
LinkType: asset.LinkType,
})
if err != nil {
return nil, false, err
}
return releaseLink, aliased, nil
}
type Context struct {
Client *gitlab.Client
IO *iostreams.IOStreams
AssetFiles []*ReleaseFile
AssetsLinks []*ReleaseAsset
}
// UploadFiles uploads a file into a release repository.
func (c *Context) UploadFiles(projectID, tagName string) error {
if c.AssetFiles == nil {
return nil
}
color := c.IO.Color()
for _, file := range c.AssetFiles {
fmt.Fprintf(c.IO.StdErr, "%s Uploading to release\t%s=%s %s=%s\n",
color.ProgressIcon(), color.Blue("file"), file.Path,
color.Blue("name"), file.Name)
r, err := file.Open()
if err != nil {
return err
}
// Ignoring SA1019 - not sure what to do yet, see https://gitlab.com/gitlab-org/cli/-/merge_requests/1895#note_2331674215.
//nolint:staticcheck
projectFile, _, err := c.Client.Projects.UploadFile(
projectID,
r,
file.Name,
nil,
)
if err != nil {
return err
}
releaseAsset := c.CreateReleaseAssetFromProjectFile(file, projectFile)
_, _, err = CreateLink(c.Client, projectID, tagName, releaseAsset)
if err != nil {
return err
}
}
c.AssetFiles = nil
return nil
}
func (c *Context) CreateReleaseAssetLinks(projectID string, tagName string) error {
if c.AssetsLinks == nil {
return nil
}
color := c.IO.Color()
for _, asset := range c.AssetsLinks {
releaseLink, aliased, err := CreateLink(c.Client, projectID, tagName, asset)
if err != nil {
return err
}
fmt.Fprintf(c.IO.StdErr, "%s Added release asset\t%s=%s %s=%s\n",
color.GreenCheck(), color.Blue("name"), *asset.Name,
color.Blue("url"), releaseLink.DirectAssetURL)
if aliased {
fmt.Fprintf(c.IO.StdErr, "\t%s Aliased deprecated `filepath` field to `direct_asset_path`. Replace `filepath` with `direct_asset_path`\t%s=%s\n",
color.WarnIcon(),
color.Blue("name"), *asset.Name)
}
}
c.AssetsLinks = nil
return nil
}
// aliasFilePathToDirectAssetPath ensures that the Asset Link uses the direct_asset_path and not the filepath.
// The filepath is deprecated and will be fully removed in GitLab 17.0.
// See https://docs.gitlab.com/ee/update/deprecations.html?removal_milestone=17.0#filepath-field-in-releases-and-release-links-apis
func aliasFilePathToDirectAssetPath(asset *ReleaseAsset) (bool /* aliased */, error) {
if asset.FilePath == nil || *asset.FilePath == "" {
// There is no deprecated filepath set, so we are good.
return false, nil
}
if asset.DirectAssetPath != nil && *asset.DirectAssetPath != "" {
// Both, filepath and direct_asset_path are set, so we have a conflict
return false, &ConflictDirectAssetPathError{asset.Name}
}
// We use the set filepath as direct_asset_path
// and clear the filepath to net send it via API.
asset.DirectAssetPath = asset.FilePath
asset.FilePath = nil
return true, nil
}
// CreateReleaseAssetFromProjectFile creates a ReleaseAsset from a ProjectFile and the metadata from a ReleaseFile.
// See [https://docs.gitlab.com/api/projects/#upload-a-file](https://docs.gitlab.com/api/project_markdown_uploads/)
func (c *Context) CreateReleaseAssetFromProjectFile(releaseFile *ReleaseFile, projectFile *gitlab.ProjectFile) *ReleaseAsset {
baseURL := c.Client.BaseURL()
// projectFile.FullPath contains the absolute path of the file within the
// context of the client's baseURL.
//
// It has a structure like so:
// FullPath: /-/project/[project_id]/uploads/[hash]/[file]
// Or on older GitLab prior to 17:
// FullPath: /[namespace]/[project]/uploads/[hash]/[file]
//
// Thus, we append it to the base URL to get a usable link.
//
// See for context [https://docs.gitlab.com/ee/api/projects.html#upload-a-file](https://docs.gitlab.com/api/project_markdown_uploads/)
baseURL.Path = projectFile.FullPath
linkURL := baseURL.String()
filename := "/" + releaseFile.Name
return &ReleaseAsset{
Name: &releaseFile.Label,
URL: &linkURL,
DirectAssetPath: &filename,
LinkType: releaseFile.Type,
}
}